【问题标题】:Remove nodes of a linked list on condition (C)在条件 (C) 上删除链表的节点
【发布时间】:2014-07-01 17:07:55
【问题描述】:

我正在使用链表进行练习,其中我需要根据条件删除一些节点。条件是''删除存储的数据小于或等于'avg'的节点。

我应该考虑三种情况: - 移除头节点 - 删除中间的一个节点 - 删除最后一个节点

我使用了三个指针,但这似乎不起作用。我究竟做错了什么?我是否不小心遗漏了一些指针?提前谢谢!

void checkAvg(int avg, struct list* head_node){

   struct list* prev;
   struct list* curr;
   struct list* next;

   prev = head;
   curr = prev->link;
   next = curr->link;

   while (curr!=NULL) {
      if (prev->data <= avg) {
          head = curr;
      }

      if (curr->data <= avg) {
          prev->link = curr->link;
      }

      if (next->data <= avg) {
          curr->link = next->link;
      } 

      prev = prev->link;
      curr = curr->link;
      next = next->link;

   }

}

编辑: 对不起,正如你告诉我的,我没有具体说明。该程序必须这样做: 1)读取一个链表 2) 生成我读取的值的输出平均值(值的总和 / 值的数量) 3) 从列表中删除具有

感谢所有回复的人,我现在正在查看回复

【问题讨论】:

  • head_node = NULL 时你的代码会发生什么?
  • (a) 当您的列表为 empty 时,head_node 持有什么? (b) 当head_node 指向的初始节点是您要删除的节点时会发生什么? (c) 是否有意没有释放实际内存并且您尝试删除的节点只是泄漏到以太中?简而言之,您没有提供足够的关于您选择显示的代码的信息,以使我们能够准确了解您的列表是如何管理的,因此您得到的任何答案都不会出现明显错误- 最好的摇摆。
  • 什么“似乎不起作用”?
  • 我想回复大家:hacks while 循环应该给出一个段错误,所以需要修复我猜@WhozCraig A)是的,我只是注意到我需要一个条件 B)我以为我在第一个 if 条件下解决了这个问题,但我想它也不起作用 C) 不,这不是故意的,我想稍后再处理它,因为 DanFego,实际上它似乎不起作用删除不应删除的节点。我现在正在编辑帖子以提供更多信息
  • @WhozCraig 刚刚在我的帖子中发布了更正

标签: c list linked-list


【解决方案1】:

如果您的链接列表没有有一些前哨头节点(我强烈建议它没有,因为NULL 是一个非常好的值来表示“我是空的”) , 移除遍历并不是很复杂。您的代码中似乎误入歧途的地方是:

  • 按地址传递头指针并将参数声明为指向指针的指针如果旧的头指针被释放,则返回新的头指针。
  • 保持本地指针变量的一致性。您必须确定所有事情都指向在任何时候

您可以使用指针,也可以使用实际的指针本身(按地址)。我更喜欢后者。在任何一种情况下,头节点指针必须按地址传递,以允许对调用者的变量进行潜在修改(就像 C 中的所有其他内容一样),或者函数可以返回潜在的新头节点地址。我更喜欢这些方法中的前一种,因为它让您可以选择使用函数返回值将错误状态传达给调用者。你现在没有这样做,但你应该(提示)。

void checkAvg(int avg, struct list** pp)
{
    while (*pp)
    {
        if ((*pp)->data <= avg)
        {
            struct list *victim = *pp;
            *pp = victim->link;
            free(victim);
        }
        else
        {   // advance to address of next "link" pointer
            pp = &(*pp)->link;
        }
    }
}

值得注意的是,如果列表按升序进行排序,这会大大简化。如果是这种情况,整个checkAvg 函数就会变得更加简单。一旦检测到不再符合您的条件的值,您就可以退出循环:

void checkAvg(int avg, struct list** pp)
{
    while (*pp && (*pp)->data <= avg)
    {
        struct list *victim = *pp;
        *pp = victim->link;
        free(victim)
    }
}

在任何一种情况下,函数都是通过在调用方按地址传递头指针来调用的

struct list *head = NULL;

//... populate list....

checkAvg(value, &head);

工作原理

链表看起来像这样:

          --------      --------      --------
head ---> | link | ---> | link | ---> | link | ---> NULL
          | val1 |      | val2 |      | val3 |
          --------      --------      --------

使用发布的方法,遍历列表使用指针到指针,它执行如下操作:

pp --:
     :        --------      --------      --------
    head ---> | link | ---> | link | ---> | link | ---> NULL
              | val1 |      | val2 |      | val3 |
              --------      --------      --------

pp 指向一个指针不是节点。最初pp 保存head 指针的地址(通过地址作为参数传入)。

那么如果第一个节点符合您的条件会发生什么?最终,这就是结果

pp --:
     :        --------      --------
    head ---> | link | ---> | link | ---> NULL
              | val2 |      | val3 |
              --------      --------

             --------
 victim ---> | link |
             | val1 |
             --------

并且受害者节点被立即丢弃(受害者的链接实际上仍然引用第二个节点,但在分离发生后在此上下文中没有意义。

那么,如果 second 节点是需要移除的节点(即我们跳过了第一个节点)怎么办。这有点复杂:

         pp -----:
                 :
              ---:----      --------
    head ---> | link | ---> | link | ---> NULL
              | val1 |      | val3 |
              --------      --------
             --------
 victim ---> | link |
             | val2 |
             --------

这是 试图 显示的(当然很糟糕)是 pp 指针指向指针始终保存我们可能正在修改的指针的地址.如果我们不需要修改该指针,我们将pp中保存的地址更改为指向列表中的下一个link指针(通过pp = &amp;(*pp)-&gt;link获得

当列表已经排序时,后一种情况不会发生,因此它的迭代循环更简单。我们只是枚举列表,丢弃节点,直到找到不再满足条件的节点。

但无论如何,pp 始终持有指向我们正在使用的节点的指针的地址。。最初,该地址是调用者的head 指针的地址。

好的。我只能希望让它更清楚。在循环中使用printf("pp=%p, *pp=%p\n", pp, *pp) 进行一些调试使得实际发生的事情最具教育意义。在调试器中手动遍历算法会高度提供丰富的信息。

【讨论】:

  • 谢谢@WhozCraig,这不仅有效,而且详细解释了我的一切。为了清楚起见,我理解英语和代码,您正在做的是:1)搜索满足条件的节点 2)将节点复制到“受害者”之一 3)现在 pp 链接到受害者->链接指向。我不明白,如果受害者是pp,他们不应该指向同一个节点吗?
  • @Roberto 关闭。我会添加一个解释,如果我有时间,一些 ascii-art(虽然我的有点蹩脚)。我正要去开会,但如果我觉得无聊,我会启动笔记本电脑并写点什么。我现在告诉你,没有节点 复制 完成。这里唯一移动的是指针中的地址。
  • -1 不起作用:您不更新先前节点的链接,因此如果您删除列表中间的元素,您将继续指向它。
  • @NicolasDefranoux 你完全错了。删除 any 元素会直接通过*pp 更新最初到达那里的实际指针(可能是头节点,或者可能是前一个节点的link 成员),其中@987654342 @ 持有上述指针地址。
  • 我的错,你是对的。如果您编辑答案,我将删除我的反对票(现在已锁定)。
【解决方案2】:

这比您想象的要容易得多。

您正在遍历和修改链表,因此请设置当前和上一个。

void checkAvg(int avg, struct list** head_node){ //when calling, use checkAvg(avg, &head_node);
    struct list* prev = NULL; 
    struct list* curr = *head_node; 

从头开始...

while(curr != NULL){ 
    if(curr->data <= avg){
         if(prev == NULL){
             *head_node = curr->next; //updates the head node
         } else {
             prev->next = curr->next; //removes the unwanted node
         }
    }
    curr = curr->next;
}

您真的不需要特殊的结束情况,因为当 curr 为 NULL 时 while 循环终止;对于列表中的最后一项,curr->next 为 NULL,因此当它被设置时,它将结束循环。您也不需要检查列表是否为空,因为如果它是curr == NULL 并且您首先将头部分配给curr,则循环将结束。

【讨论】:

  • 你好,我明白你在告诉我什么,但它不起作用。基本上它打印一个空列表。我使用了 checkAvg(avg,&head) 但我得到了一个结束但有一个空列表的程序。附:为什么我们需要一个头部的双指针?
  • 你需要一个双指针,因为你想更新头部,以防你移除头部。打印一个空列表是什么意思?
  • 我在列表中插入值:1)我的程序打印列表和平均值,2)我使用 checkAVG,我要求程序再次打印列表 3)它不打印,但只是结束程序
【解决方案3】:

今晚我想要一些递归魔法

Node* deleteBad(Node *head, int(*pred)(int)) {
    if (head == NULL) {
        return NULL;
    }
    if (pred(head->value)) {
        head->next = deleteBad(head->next, pred);
        return head;
    }
    else {
        Node *next = head->next;
        free(head);
        return deleteBad(next, pred);
    }
}

和累加器

void deleteBad2(Node *head, int(*pred)(int), Node *out) {
    if (head) {
        if (pred(head->value)) {
            out->next = head;
            deleteBad2(head->next, pred, out->next);
        } else {
            out->next = head->next;
            deleteBad2(head->next, pred, out);
        }
    } 
}

对于那些不喜欢递归优化版本的花花公子

void deleteBad3(Node *head, int(*pred)(int), Node *out) {
begin:
    if (head) {
        if (pred(head->value)) {
            out->next = head;
            out = out->next;
            head = head->next;
            goto begin;
        }
        else {
            out->next = head->next;
            head = head->next;
            goto begin;
        }
    }
}

如果有人不喜欢 gotos

void deleteBad3nogoto(Node *head, int(*pred)(int), Node *out) {
    while (1) {
        if (head) {
            if (pred(head->value)) {
                out->next = head;
                out = out->next;
                head = head->next;

            }
            else {
                out->next = head->next;
                head = head->next;

            }
        }
        else {
            break;
        }
    }
}

【讨论】:

  • 你不喜欢递归,你这个怪物
  • 你好,很抱歉这个愚蠢的问题,它如何检查值是否小于或等于平均值​​?
  • 它使用函数指针。您应该定义类似 int f(int a) {return a>2;} 的函数并将其作为参数发送给函数 deleteBad(list, f);
【解决方案4】:

您在每次迭代中检查每个节点 3 次,这会导致一些奇怪的行为。进入循环前检查头部,每次只比较当前节点:

void checkAvg(int avg, struct list* head_node){

   struct list* prev;
   struct list* curr;


   while (head!=NULL) {
      if (head->data <= avg) {
          head = head->link;
      } else {
         break;
      }
   }

   prev = head;
   curr = prev->link;

   while (curr!=NULL) {

      if (curr->data <= avg) {
          prev->link = curr->link;

      }
      prev = prev->link;
      curr = prev->link;
   }
}

【讨论】:

    【解决方案5】:

    不检查prev、curr和next的数据,只检查循环中的一个数据。
    您还需要返回新的头部以防它发生变化。
    我会建议类似的东西(经过测试):

    #include <stdio.h>
    #include <malloc.h>
    
    typedef struct list_ {
      struct list_ *link;
      int data;
    } list;
    
    list* RemoveElementsBelow(int avg, list* head_node) {
      while (head_node != NULL && head_node->data <= avg) {
        // Remove the first.
        list* new_head = head_node->link;
        free(head_node);
        head_node = new_head;
      }
      if (head_node == NULL) {
        return NULL;
      }
    
      list* prev;
      list* curr;
    
      prev = head_node;
      curr = prev->link;
    
      while (curr != NULL) {
        if (curr->data <= avg) {
          prev->link = curr->link;  // Will be NULL at the end of the list.
          free(curr);
          curr = prev->link;
        } else {
          prev = curr;
          curr = curr->link;
        }
      }
      return head_node;
    }
    
    list* AddToHead(int value, list* head_node) {
      list* new_node = malloc(sizeof(list));
      new_node->link = head_node;
      new_node->data = value;
      return new_node;
    }
    
    list* PrintAndDeleteList(list* head_node) {
      while (head_node != NULL) {
        list* new_head = head_node->link;
        printf("%d ", head_node->data);
        free(head_node);
        head_node = new_head;
      }
      printf("\n");
    }
    int main(int argc, char **argv) {
      list* my_list = NULL;
      int i;
      int sum = 0;
      for (i = 1; i < argc; ++i) {
        int value = atoi(argv[i]);
        sum += value;
        my_list = AddToHead(value, my_list);
      }
      if (argc == 1) {
        return 1;
      }
      int avg = sum / (argc - 1);
      printf("Average value: %d\n", avg);
    
      my_list = RemoveElementsBelow(avg, my_list);
    
      PrintAndDeleteList(my_list);
      return 0;
    }
    

    编译:

    gcc -o test test.c
    

    测试: ./测试 10 20 30 40 50 平均值:30 50 40 ./测试 50 40 30 20 10 平均值:30 40 50

    【讨论】:

    • 你好@Nicolas,刚刚测试了这个,但似乎列表保持不变
    • 你用 head_node = checkAvg(value, head_node) 调用它吗?您是否尝试过删除开头、中间、结尾和几个元素的元素?
    • 是的,以列表为例: 10 20 30 40 50 平均值为 30 我调用 checkAvg(average, head) 它打印 10 20 30 40 50 而不是 40 50
    • 我刚刚更新了答案以添加测试代码。看起来很有效。
    猜你喜欢
    • 2019-05-10
    • 2021-07-25
    • 1970-01-01
    • 1970-01-01
    • 2016-01-02
    • 2016-03-02
    • 1970-01-01
    • 2015-01-30
    • 2017-10-21
    相关资源
    最近更新 更多