在条件(C)下删除链表的节点

时间:2014-07-01 17:07:55

标签: c list linked-list

我正在使用链表进行练习,其中我需要根据条件删除一些节点。条件是''删除存储的数据小于或等于'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)从列表中删除具有&lt; =平均值的数据的节点 释放那些节点的内存。 关于free()部分,我遇到了一些困难,所以我想我首先要学习如何正确使用指针。你可能已经注意到了,我并不擅长这一点,我是首发。

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

5 个答案:

答案 0 :(得分:5)

如果您的链接列表有一些标记头节点(我强烈建议它没有,因为NULL是一个非常好的值,表明&#34;我& #39; m empty&#34;),删除遍历不是太复杂。代码中您似乎误入歧途的地方是:

  • 将头指针传递给地址,并将参数声明为指针指向 OR ,如果处理旧指针,则返回新的指针。
  • 维护本地指针变量的一致性。你必须知道肯定所有内容始终指向

您可以使用指针,也可以使用实际指针本身(按地址)。我更喜欢后者。在 的情况下,头节点指针必须通过地址传递以允许可能修改调用者的变量(就像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)
    }
}

在任何一种情况下,通过将头指针传递给调用方 by-address 来调用该函数:

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指针的地址(作为参数传入by-address)。

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

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

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

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

那么,如果第二个节点是需要删除的节点(即我们跳过第一个节点),那该怎么办呢?这有点复杂:

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

这是尝试显示(确实很差)的是pp指向指针总是保存我们可能指针的地址正在修改。如果我们不需要修改该指针,我们会将pp中的地址更改为指向列表中的下一个link指针(通过pp = &(*pp)->link获取

当列表已经排序时,后一种情况不会发生,因此其迭代循环更简单的原因。我们只是枚举列表,抛出节点,直到找到一个不再符合我们条件的节点。

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

确定。我只能希望让它更清晰。在循环中使用printf("pp=%p, *pp=%p\n", pp, *pp)进行一些调试可以实现最具教育意义的实际情况。在调试器中手动遍历算法将高度提供信息。

答案 1 :(得分:1)

比你想象的容易得多。

您正在步行和修改链接列表,因此请设置当前和之前的链接。

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-&gt; next为NULL,因此当它被设置时它将结束循环。您也不需要检查列表是否为空,因为如果curr == NULL,则循环将结束,并且您首先将头部分配给curr

答案 2 :(得分:0)

不是检查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

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

答案 3 :(得分:0)

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

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;
   }
}

答案 4 :(得分:0)

我今晚想要一些递归魔法

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);
        }
    } 
}

对于那些不喜欢递归优化版本的coxcombs

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;
        }
    }
}

如果有人不喜欢搞笑

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;
        }
    }
}