Я написал код удаления узла путем сравнения его данных с предоставленными данными. Если равно, он удаляет узел. Моя проблема в том, что мой код удалит каждый узел в списке, кроме последнего узла. Я не мог понять, что делаю неправильно, поэтому обратился за помощью, как обычно.
Код прилагается. Надеюсь, кто-нибудь мне поможет.
(редактировать: код изменен в соответствии с ошибками)
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;
}
}
Если в списке есть один элемент (так (*head)->next == NULL
), вы удаляете элемент независимо от того, соответствует он данным или нет. Это нежелательно.
Давайте попробуем что-нибудь простое.
Найдите элемент, который нужно удалить.
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?
Удали это.
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
Пустое пространство обходится дешево и облегчает чтение вашего кода. Например, вместо
if (temp->data==data)
используйтеif (temp->data == data)
. Обратите внимание, что операторы точка.
и стрелка->
очень тесно связаны (как и индексы массива); вокруг них не должно быть пространств.