【问题标题】:Remove all occurences of an element in a one-way linked list [closed]删除单向链表中所有出现的元素[关闭]
【发布时间】:2018-06-04 07:17:01
【问题描述】:

我花了几个小时编写了一个函数——给定一个单向链表,它将删除给定元素的所有出现。这是我设法编写的函数:

void remove_by_value(struct Node **head, int value) {
    while( *head!=NULL && (*head)->value == value ) {
        struct Node *tmp = *head;
        if((*head)->next != NULL)
            *head = (*head)->next;
        else
            *head = NULL;
        free(tmp);
    }

    struct Node *iterator = *head;
    while(iterator->next != NULL) {
        if(iterator->next->value == value) {
            struct Node *tmp = iterator;
            iterator = iterator->next;
            free(tmp);
            continue;
        }
        iterator = iterator->next;
    }
}

在我的示例中,我使用了一个如下所示的简单列表:2->1->1 以及运行remove_by_value(&head, 1) 后程序的输出:

0 
1242177584 
Segmentation fault (core dumped)

这与预期的效果相去甚远。问题是我不明白我的错误在哪里。这是我想应用的算法:

  1. 检查此列表的头部是否具有所需的值。如果是这样,将头部向右移动并删除第一个节点。重复此操作,直到新 head 的值与此函数的参数不同
  2. 检查下一个节点的值是否等于所需值。如果是,则删除该节点并将当前节点与已删除节点之后的节点链接。

我知道我的算法并不完美——例如它不能处理节点不指向任何东西的情况。但问题的核心是我不明白我的程序为什么会这样。你能给我一些建议吗?有没有更好的方法来编写这个算法?

编辑:
我认为这可能很重要,因此我决定包含我的打印功能,因为根据我之前的功能,它可能会导致一些潜在的内存泄漏:

void print(struct Node *head) {
    while(head != NULL) {
        printf("%d \n", head->value);
        head = head->next;
    }
}

【问题讨论】:

  • 我相信您应该手动检查您在问题陈述中给出的示例的代码 (2->1->1)。您的代码仅在您的第一个 while 循环的第一个条件检查中失败(TRUE && FALSE),即它根本没有进入您的第一个 while 循环。在第二个while 循环中,你释放了tmp,它当前指向2
  • 我只是好奇。为什么你的程序会有输出? 0 和 1242177584 是什么意思?
  • @leyanpan 我运行了函数,然后运行了我包含的打印函数。
  • 你还应该花几个小时调试你的代码,单步执行,检查值等等。这就是开发软件的方式:(
  • @Aemilius 你应该发布一个minimal reproducible example 并告诉我们你的程序的输入是什么(如果有的话)。

标签: c list data-structures linked-list singly-linked-list


【解决方案1】:

我认为这应该可行,但太复杂而无法验证。如果没有,将删除该帖子。

void remove_by_value(struct Node **head, int value) {
    while( *head!=NULL && (*head)->value == value ) {
        struct Node *tmp = *head;
        *head = (*head)->next;     /*removed some useless code here*/
        free(tmp);
    }
    struct Node *iterator = *head;
    if(iterator == NULL)
        return;
    while(iterator->next != NULL) {
        if(iterator->next->value == value) {
            struct Node *tmp = iterator->next;
            iterator->next = iterator->next->next; 
            free(tmp);
        }
        iterator = iterator->next;
    }
}

解释(感谢下面的cmets):

原始代码测试是否应该删除 iterator->next 但错误地删除了 iterator it self。例如2->1->1,2会被删除,因为下一个节点是1,但实际上应该删除的是1。

删除链表中的元素时,仅释放应该删除的节点是不够的。还需要将前一个节点的“next”变量设置为被删除元素之后的节点。

例如2->1->3 OP的代码:2->Null Nothing->3;正确:2->3

附: 我的答案有问题,但似乎无法删除已接受的答案。

下面的更好答案: https://stackoverflow.com/users/2877241/vlad-from-moscow

你的函数太复杂并且有一个错误,因为在第二个 while 循环中没有检查 head 是否等于 NULL 并且循环更改了局部变量迭代器而不是更改节点本身。

该功能可以更简单地实现。例如

void remove_by_value( struct Node **head, int value ) 
{
    while ( *head )
    {
        if ( ( *head )->value == value )
        {
            struct Node *tmp = *head;
            *head = ( *head )->next;
            free( tmp );
        }
        else
        {
            head = &( *head )->next;
        }
    }
}

这是一个演示程序

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

struct Node
{
    int value;
    struct Node *next;
};

void insert(struct Node **head, const int a[], size_t n)
{
    for (size_t i = 0; i < n; i++)
    {
        struct Node *current = ( struct Node * )malloc(sizeof(struct Node));

        current->value = a[i];
        current->next = *head;

        *head = current;
        head = &(*head)->next;
    }
}

void print( struct Node *head ) 
{
    for ( ; head != NULL; head = head->next )
    {
        printf( "%d ", head->value );
    }
}

void remove_by_value( struct Node **head, int value ) 
{
    while ( *head )
    {
        if ( ( *head )->value == value )
        {
            struct Node *tmp = *head;
            *head = ( *head )->next;
            free( tmp );
        }
        else
        {
            head = &( *head )->next;
        }
    }
}

int main(void) 
{
    struct Node *head = NULL;
    int a[] = { 2, 1, 1 };

    insert( &head, a, sizeof( a ) / sizeof( *a ) );

    print( head );
    putchar( '\n' );

    remove_by_value( &head, 1 );

    print( head );
    putchar( '\n' );

    remove_by_value( &head, 2 );

    print( head );
    putchar( '\n' );

    return 0;
}

它的输出是

2 1 1 
2 

【讨论】:

  • 是的,它有效,我最终找到了相同的解决方案:-)
  • 为什么这个版本能用,但是OP代码不能用?纯代码答案对未来的读者不是很有用。
  • @leyanpan 这只是一个糟糕的代码,仅此而已。
  • @Aemilius 你错了。它不起作用。
  • 你能说一下为什么,这样我还是可以学习的。 @vlad-from-moscow
【解决方案2】:

所以删除函数的第一个循环是可以的,但有点笨拙。这段代码非常多余,因为无论next 是什么值,您都将其分配给*head

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

但它所做的只是删除第一个节点,同时它与您正在寻找的值匹配,这并不是特别有用。

问题在于下一个循环,因为它没有正确更新您的列表。想象一下你的列表是 1 -> 2 -> 3 并且你正在删除 2。你遍历你的列表直到你到达中间节点。您获取当前节点的临时副本,将自己指向下一个节点,然后释放它。哪个让你的列表像 1 -> 2 ??? 3 因为您还没有更新前一个节点以指向下一个节点。

你可以只用一个循环来完成整个事情。您使用head 作为跟踪指向当前节点的任何内容的一种方式。所以在循环的开始它指向列表的开始。如果节点不匹配,则将其指向当前节点的 next 指向的任何位置。

如果匹配,您再次获取当前节点的临时副本,但这次您更新*head 以指向下一个节点。所以它会保持之前的连接或列表的开始。

void remove_by_value(struct Node **head, int value) {
    while( *head!=NULL ) {
        if ((*head)->value == value) {
            struct Node *tmp=*head;
            *head=(*head)->next;
            free(tmp);
        } else {
            head=&((*head)->next);
        }
    }
}

【讨论】:

    【解决方案3】:

    您的函数太复杂并且有一个错误,因为在第二个 while 循环中没有检查 head 是否等于 NULL,并且循环更改了局部变量 iterator 而不是更改节点本身。

    该功能可以更简单地实现。例如

    void remove_by_value( struct Node **head, int value ) 
    {
        while ( *head )
        {
            if ( ( *head )->value == value )
            {
                struct Node *tmp = *head;
                *head = ( *head )->next;
                free( tmp );
            }
            else
            {
                head = &( *head )->next;
            }
        }
    }
    

    这是一个演示程序

    #include <stdio.h>
    #include <stdlib.h>
    
    struct Node
    {
        int value;
        struct Node *next;
    };
    
    void insert(struct Node **head, const int a[], size_t n)
    {
        for (size_t i = 0; i < n; i++)
        {
            struct Node *current = ( struct Node * )malloc(sizeof(struct Node));
    
            current->value = a[i];
            current->next = *head;
    
            *head = current;
            head = &(*head)->next;
        }
    }
    
    void print( struct Node *head ) 
    {
        for ( ; head != NULL; head = head->next )
        {
            printf( "%d ", head->value );
        }
    }
    
    void remove_by_value( struct Node **head, int value ) 
    {
        while ( *head )
        {
            if ( ( *head )->value == value )
            {
                struct Node *tmp = *head;
                *head = ( *head )->next;
                free( tmp );
            }
            else
            {
                head = &( *head )->next;
            }
        }
    }
    
    int main(void) 
    {
        struct Node *head = NULL;
        int a[] = { 2, 1, 1 };
    
        insert( &head, a, sizeof( a ) / sizeof( *a ) );
    
        print( head );
        putchar( '\n' );
    
        remove_by_value( &head, 1 );
    
        print( head );
        putchar( '\n' );
    
        remove_by_value( &head, 2 );
    
        print( head );
        putchar( '\n' );
    
        return 0;
    }
    

    它的输出是

    2 1 1 
    2 
    

    【讨论】:

      【解决方案4】:

      曾经有一段时间,程序员不得不经常做这种事情,而且当你做了足够多的时间之后,你的代码就会变得非常高效。为了帮助保持这种古老艺术的活力,这样的函数应该是这样的:

      void removeByValue(Node **head, int val){
          Node *n;
          while(n=*head) {
              if (n->value == val) {
                  *head = n->next;
                  free(n);
              } else {
                  head = &(n->next);
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2017-12-28
        • 2021-05-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-12
        • 1970-01-01
        • 2020-03-16
        • 1970-01-01
        相关资源
        最近更新 更多