【问题标题】:Deleting first node in linked list has problems删除链表中的第一个节点有问题
【发布时间】:2013-10-05 07:14:51
【问题描述】:

我正在实现一个链表,它需要一个函数,当给定链表的头部和一个 cstring 时,它会找到并删除一个值为 cstring 的节点。

typedef struct node
{
  char entry[21];
  struct node* next;
} node;


/*returns true if node with phrase value found, otherwise false*/
bool findAndRemove(node* root, char phrase[21])
{
    if(root != NULL)
    {
        node* previous = NULL;
        while(root->next != NULL)
        {
            if(strcmp(root->entry, phrase) == 0)//found
            {
                if(previous == NULL)//node to delete is at head
                {
                    node* tmp = root;
                    root = root->next;
                    free(tmp);
                    return true;
                }
                previous->next = root->next;
                free(root);
                return true;
            }
            previous = root;
            root = root->next;
        }
        return false;
    }
}

它工作正常,但是在删除头部时会打印出一些垃圾。发生了什么,我该如何解决?我有内存泄漏吗?出于好奇,“根”或“头”这个词更常用于链表中的第一个节点吗?

【问题讨论】:

  • 我会说“head”更适合链表,我会使用 root 来表示树状结构

标签: c memory-leaks linked-list


【解决方案1】:

首先要意识到的是,从链表中删除一个元素涉及更改恰好一个指针值:指向我们的指针。这可以是指向第一个列表元素的外部head 指针,也可以是列表内的->next 指针之一。在这两种情况下,指针都需要更改;它的新值应该成为要删除的节点的->next指针的值。

为了改变某个对象(从函数内部),我们需要一个指向它的指针。我们需要改变一个指针,所以我们需要一个指向指针的指针

bool findAndRemove1(node **ptp, char *phrase)
{
    node *del;

    for( ;*ptp; ptp = &(*ptp)->next) {
        if( !strcmp((*ptp)->entry, phrase) ) { break; } //found
        }

      /* when we get here, ptp either
      ** 1) points to the pointer that points at the node we want to delete
      ** 2) or it points to the NULL pointer at the end of the list
      **    (in the case nothing was found)
      */
    if ( !*ptp) return false; // not found

    del = *ptp;
    *ptp = (*ptp)->next;
    free(del);
    return true;
}

if 条件的数量甚至可以通过在循环中做脏活来减少到一个,然后从循环中返回,但这有点小技巧:

bool findAndRemove2(node **ptp, char *phrase)
{

    for( ;*ptp; ptp = &(*ptp)->next) {
        node *del;
        if( strcmp((*ptp)->entry, phrase) ) continue; // not the one we want

          /* when we get here, ptp MUST
          ** 1) point to the pointer that points at the node we want to delete
          */
        del = *ptp;
        *ptp = (*ptp)->next;
        free(del);
        return true;
        }
    return false; // not found
}

但是如果列表不是唯一的,我们想删除所有满足条件的节点呢?我们只是稍微改变一下循环逻辑并添加一个计数器:

unsigned searchAndDestroy(node **ptp, char *phrase)
{
    unsigned cnt;

    for( cnt=0 ;*ptp; ) {
        node *del;
        if( strcmp((*ptp)->entry, phrase) ) { // not the one we want
             ptp = &(*ptp)->next;
             continue; 
             }
          /* when we get here, ptp MUST point to the pointer that points at the node we wish to delete
          */
        del = *ptp;
        *ptp = (*ptp)->next;
        free(del);
        cnt++;
        }
    return cnt; // the number of deleted nodes
}

更新:以及测试它的驱动程序:

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

typedef struct  list {
        struct list *next;
        char entry[20];
        } node;

void node_add( node **ptp, char *str)
{
node *new;

for (   ; *ptp; ptp = &(*ptp)->next) {
        if (strcmp ((*ptp)->entry, str) < 0) continue;
        }
new = malloc (sizeof *new);
strcpy(new->entry, str);
new->next = *ptp;
*ptp = new;
}

int main (void)
{
node *root = NULL;
unsigned cnt;

node_add (& root, "aaa" );
node_add (& root, "aaa" );
node_add (& root, "bbb" );
node_add (& root, "ccc" );
node_add (& root, "aaa" );
cnt = seachAndDestroy( &root, "bbb" );
printf("Cnt(bbb) := %u\n", cnt );
cnt = seachAndDestroy( &root, "ccc" );
printf("Cnt(ccc) := %u\n", cnt );
cnt = seachAndDestroy( &root, "aaa" );
printf("Cnt(aaa) := %u\n", cnt );
printf("Root now = %p\n", (void*) root );

return 0;
}

还有输出:

plasser@pisbak:~/usenet$ ./a.out
Cnt(bbb) := 1
Cnt(ccc) := 1
Cnt(aaa) := 3
Root now = (nil)

【讨论】:

  • @wildplasser 我认为searchAndDestroy() 中有一个错误。我的程序遇到了未定义的行为,当我注释掉这个函数时它停止了。不知何故,节点被设置为空。导致这种情况的输入是当phrase 的值在连续两次调用中相同并且该值由所有相同的字母组成时。例如searchAndDestroy(根,“aaaa”); searchAndDestroy(根,“aaaa”);导致未定义的行为
  • 错误可能在其他地方。例如,另一个函数在初始化或删除时未将 -&gt;next 设置为 NULL。或者链表可以包含一个循环。这种“损坏数据”错误出现在意想不到的地方并不罕见。并且可以预期,当您从不删除任何内容时,您会遇到更少的问题。
【解决方案2】:

您正在更改函数内部的根,因此您需要传递一个双指针:

bool findAndRemove(node** root, char phrase[21])
{
    node* iterate = *root;
    if(root != NULL && *root != NULL)
    {
        node* previous = NULL;
        while(iterate->next != NULL)
        {
            if(strcmp(iterate->entry, phrase) == 0)//found
            {
                if(previous == NULL)//node to delete is at head
                {
                    node* tmp = iterate;
                    *root = iterate->next;
                    free(tmp);
                    return true;
                }
                previous->next = iterate->next;
                free(iterate);
                return true;
            }
            previous = iterate;
            iterate = iterate->next;
        }
        return false;
    }
}

【讨论】:

  • 此代码不起作用,您在迭代时更改了引用。最后*root 将指向列表中的最后一个节点。
  • @Celeritas 删除根节点时需要修改外部指针,否则不需要..
  • 您的回答中有太多不必要的特殊情况。查看我的解决方案(仅包含两个 if 语句,没有 else
  • @ShimonRachlenko:...我使用了你的(并删除了其中的 2/3)不是开源的一件好事:-?
【解决方案3】:

你通过指向第一个节点来构造一个列表。

然后你删除第一个节点,但是不要更新指向列表的指针指向第二个节点

只需让您的函数检查您是否正在删除第一个节点,并始终返回指向最终列表的第一个指针的指针。或者,代替node *root 参数,传递node **root 以便您可以修改函数中的引用(尽管我不喜欢这种工作方式)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多