Удаление выбранного элемента в двусвязном списке

Я написал код удаления узла путем сравнения его данных с предоставленными данными. Если равно, он удаляет узел. Моя проблема в том, что мой код удалит каждый узел в списке, кроме последнего узла. Я не мог понять, что делаю неправильно, поэтому обратился за помощью, как обычно.

Код прилагается. Надеюсь, кто-нибудь мне поможет.

(редактировать: код изменен в соответствии с ошибками)

int dl_delete_element(Dlist **head, Dlist **tail, int data)
{
    Dlist *temp,*prev;
    if (*head==NULL)
        return FAILURE;
    temp=*head;
    if (temp->data==data)
    {
        prev=*head;
        (temp->next)->prev=NULL;
        *head=temp->next;
        free(prev);
        return SUCCESS;
    }
    else
    {
        while(temp!=NULL)
        {
            if (temp->data==data)
            {
                prev=temp;
                (temp->prev)->next=temp->next;
                (temp->next)->prev=temp->prev;
                free(prev);
                return SUCCESS;
            }
            else
                temp=temp->next;
        }
        return DATA_NOT_FOUND;
    }
}

Пустое пространство обходится дешево и облегчает чтение вашего кода. Например, вместо if (temp->data==data) используйте if (temp->data == data). Обратите внимание, что операторы точка . и стрелка -> очень тесно связаны (как и индексы массива); вокруг них не должно быть пространств.

Jonathan Leffler 26.06.2024 20:58

Если в списке есть один элемент (так (*head)->next == NULL), вы удаляете элемент независимо от того, соответствует он данным или нет. Это нежелательно.

Jonathan Leffler 26.06.2024 21:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
89
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Давайте попробуем что-нибудь простое.

  1. Найдите элемент, который нужно удалить.

     Dlist* cur = *head;
     while (cur != NULL && cur->data != data) 
         cur = cur->next;
     if (cur == NULL) 
         return FAILURE; // or DATA_NOT_FOUND? What other failures are there?
    
  2. Удали это.

     if (cur == *head) 
        *head = cur->next;
     else 
        cur->prev->next = cur->next;
    
     if (cur == *tail) 
        *tail = cur->prev;
     else 
        cur->next->prev = cur->prev;
    
     free(cur);
     return SUCCESS;
    

Выглядит неплохо? Предостережение: этот код не проверен. Если вы нашли ошибку, исправьте ее!

С помощью оператора (temp->next)->prev=temp->prev; вы разыменовываете temp->next без предварительной проверки, не является ли этот указатель нулевым. Оно будет нулевым, если temp является хвостовым узлом. В этом случае вы получите неопределенное поведение. Вместо этого вам следует обновить ссылку tail.

Другая проблема в вашем коде заключается в том, что, когда в списке есть только один элемент, вы удаляете этот элемент, даже не проверив, совпадают ли данные.

Я бы не вернулся FAILURE, когда список пуст. В этом нет ничего плохого; просто вы не найдете узел с целевыми данными, поэтому в этом случае просто верните DATA_NOT_FOUND.

Я бы также избегал повторения кода и разделил функциональность на отдельные функции, например, функцию для поиска узла и функцию для удаления узла по его ссылке. В частности, у вас должно быть только одно место, где вы освобождаете узел.

Вот поправка:

// Delete a node given its reference (not data):
void dl_delete_node(Dlist **head, Dlist **tail, Dlist *node) {
    if (node == *head) { // When it is the first node
        *head = node->next;
        if (*head) {
            (*head)->prev = NULL;
        } else {
            *tail = NULL;    
        }
    } else if (node == *tail) { // When it is the last node
        *tail = node->prev;
        (*tail)->next = NULL;
    } else { // When the node is neither first nor last
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }
    free(node);
}

// Find a node that has given data, or NULL when not found
Dlist *dl_find(Dlist *head, int data) {
    while (head && head->data != data) {
        head = head->next;
    }
    return head;
}

// Use the above two functions for removing 
//   the first node that has the given data
int dl_delete_element(Dlist **head, Dlist **tail, int data)
{
    Dlist *node = dl_find(*head, data);
    if (!node) {
        return DATA_NOT_FOUND;
    }
    dl_delete_node(head, tail, node);
    return SUCCESS;
}

Во-первых, у вас плохой дизайн списка. Помимо структуры, содержащей определение узла, вам следует ввести еще одну структуру, которая будет содержать только указатели на узлы head и tail, как, например,

typedef struct Node
{
    struct Node *prev;
    struct Node *next;
    int data;
} Node;

typedef struct Dlist
{
    struct Node *head;
    struct Node *tail'
} Dlist;

В этом случае ваше объявление функции будет выглядеть проще, например

int dl_delete_element( Dlist *list, int data );

Тем не менее, что касается вашего текущего дизайна списка, функция dl_delete_element даже не использует свой параметр Dlist **tail.

Кроме того, он содержит ошибки в других потоках управления кодом. Например, если список содержит только один узел и его элемент данных data равен параметру data, тогда этот фрагмент кода

if (temp->data==data)
{
    prev=*head;
    (temp->next)->prev=NULL;
    *head=temp->next;
    free(prev);
    return SUCCESS;
}

вызывает неопределенное поведение из-за использования нулевого указателя в этом операторе

    (temp->next)->prev=NULL;

потому что выражение temp->next возвращает нулевой указатель.

Используя ваш подход к определению функции, функцию можно определить просто следующим образом:

int dl_delete_element( Dlist **head, Dlist **tail, int data )
{
    if (*head == NULL)
    {
        return FAILURE;
    }

    while ( *head != NULL && ( *head )->data != data )
    {
        head = &( *head )->next;
    }

    if (*head == NULL)
    {
        return DATA_NOT_FOUND;
    }
    else
    {
        Dlist *current = *head;
        *head = current->next;

        if (*head == NULL)
        {
            *tail = current->prev;
        }
        else
        {
            ( *head )->prev = current->prev;
        }

        free( current );

        return SUCCESS;
    }
}

Вот демонстрационная программа. Конкретные значения используемых перечислителей SUCCESS, FAILURE и DATA_NOT_FOUND не имеют значения для определения функции.

#include <stdio.h>
#include <stdlib.h>

typedef struct Dlist
{
    struct Dlist *prev;
    struct Dlist *next;
    int data;
} Dlost;

enum { SUCCESS = 0, FAILURE = -1, DATA_NOT_FOUND = 1 };

int dl_delete_element( Dlist **head, Dlist **tail, int data )
{
    if (*head == NULL)
    {
        return FAILURE;
    }

    while ( *head != NULL && ( *head )->data != data )
    {
        head = &( *head )->next;
    }

    if (*head == NULL)
    {
        return DATA_NOT_FOUND;
    }
    else
    {
        Dlist *current = *head;
        *head = current->next;

        if (*head == NULL)
        {
            *tail = current->prev;
        }
        else
        {
            ( *head )->prev = current->prev;
        }

        free( current );

        return SUCCESS;
    }
}

int append( Dlist **head, Dlist **tail, int data )
{
    Dlost *new_node = malloc( sizeof( *new_node ) );

    if (new_node == NULL)
    {
        return FAILURE;
    }
    else
    {
        new_node->data = data;
        new_node->next = NULL;
        new_node->prev = *tail;

        if (*head == NULL) *head = new_node;
        else ( *tail )->next = new_node;

        *tail = new_node;

        return SUCCESS;
    }
}

void display( const Dlist *head )
{
    for (; head != NULL; head = head->next)
    {
        printf( "%d -> ", head->data );
    }
    puts( "null" );
}

void reverse_display( const Dlist *tail )
{
    for (; tail != NULL; tail = tail->prev)
    {
        printf( "%d -> ", tail->data );
    }
    puts( "null" );
}

int main( void )
{
    Dlist *head = NULL;
    Dlist *tail = NULL;

    const int N = 3;
    for (int i = 0; i < N; i++)
    {
        append( &head, &tail, i );
        display( head );
        reverse_display( tail );
        putchar( '\n' );
    }

    for (int i = N; i != 0; i--)
    {
        dl_delete_element( &head, &tail, i - 1 );
        display( head );
        reverse_display( tail );
        putchar( '\n' );
    }
}

Вывод программы

0 -> null

0 -> 1 -> null
1 -> 0 -> null

0 -> 1 -> 2 -> null
2 -> 1 -> 0 -> null

0 -> 1 -> null
1 -> 0 -> null

0 -> null
0 -> null

null
null

Другие вопросы по теме