【问题标题】:How to delete a node with a particular value?如何删除具有特定值的节点?
【发布时间】:2019-12-05 07:14:54
【问题描述】:

我正在尝试删除具有特定值的节点。我的节点设置方式是这样的:

typedef struct NodeStruct Node;

//struct for each office item
struct NodeStruct {
    int id;
    struct NodeStruct *next;
};


typedef struct {
  /** Pointer to the first node on the list (or NULL ). */
  Node *head;
} List;

目前,我的列表如下所示(下面的每个整数都代表来自 : 1 -> 2 -> 3 -> 4 -> 5 -> 7

我想删除 1 和 4,所以生成的链表如下所示: 2 -> 3 -> 5 -> 6 -> 7

我已经写了下面的代码。方法中key代表值,该值的节点应该被删除。

void delete(int key, List *list)
{
  //If linked list is empty (base case):
  if (list->head == NULL) {
    printf("Invalid command\n");
    exit(EXIT_BAD_INPUT);
  }

  //Store head node
  Node *temp = list->head;

  //If first node needs to be deleted (special case):
  if (key == list->head->id) {
    list->head = temp->next; //Change head
    free(temp); //Free old head
  } else {
    //Find previous node of the node to be deleted
    while (temp->id != key) {
      temp = temp->next;
    }

    //If the position is more than the number of nodes, throw exception:
    if (temp == NULL || temp->next == NULL) {
      printf("Invalid command\n");
      exit(EXIT_BAD_INPUT);
    }

    //Node temp->next is the node to be deleted
    //Store the pointer to the next of node to be deleted
    Node *next = temp->next->next;

    //Unlink the node from the linked list
    free(temp->next);
    temp->next = next;
    printf("RECORD DELETED: %d\n", key);
  }
}

当我运行此代码时,我收到Segmentation Fault: 11。有人可以帮助我删除具有特定值和列表末尾的节点吗?另外,如何检查链表中是否不存在键值?如果这是一个重复的问题,请提前道歉。

【问题讨论】:

  • 旁注。 /** Structure for the whole list, including head and tail pointers. */ 注释错误,没有尾指针。这是一个很好的例子,为什么应该删除重复代码中的内容的 cmets;他们很容易与代码不同步。相反,cmets 将解释为什么代码是这样的。
  • @Schwern 感谢您的关注,我刚刚编辑了帖子以使其更清晰。
  • 您能展示一下您是如何初始化节点的吗?如果它正在使用堆栈内存,free 将不起作用。
  • 如果它真的是一个单链表,你总是可以just do this。如果不是这样(管理尾指针、前后链表等),它会变得更加复杂。
  • @WhozCraig 这很聪明,持有指向下一个指针的指针。你能在答案中解释一下吗?

标签: c while-loop linked-list nodes infinite-loop


【解决方案1】:

指向指针方法的指针是可以的,但是使用两个沿着列表向下移动的指针确实没有什么复杂的。只要保持一致。有一个引导和跟踪指针。当线索找到要删除的元素时,使用线索更新其next。例外情况是轨迹为空时。这意味着前导指向头元素,因此请更新头指针。

/** delete node with value key from list */
void del_node (int key, List *list)
{
  for (Node *p = list->head, *q = NULL; p; q = p, p = p->next) // p leads, q trails
    if (p->data == key) {
      if (q) q->next = p->next;
      else list->head = p->next;
      free(p);
      return;
    }
}

【讨论】:

    【解决方案2】:

    如果不使用指针来跟踪前一个和下一个节点,而是使用 双指针(例如指针的地址)指向当前节点,没有特殊情况,例如

    /** delete node with value key from list */
    void del_node (int key, List *list)
    {
        Node **pn = &list->head;    /* pointer to pointer */
        Node *n = list->head;       /* pointer to node */
    
        while (n) {                 /* iterate over nodes */
            if (n->id == key) {     /* if key found */
                *pn = n->next;      /* set address to next */
                free (n);           /* delete current */
                break;              /* done */
            }
            pn = &n->next;          /* address of next */
            n = n->next;
        }
    }
    

    Linus on Understanding Pointers 上有一篇很好的文章说明了为什么这会使事情变得更简单。

    【讨论】:

      【解决方案3】:

      为此,您需要找到匹配的音符及其前一个节点。

      我们可以循环直到找到匹配项或列表末尾,记住前一个节点。从list->head 开始,第一个节点不需要特殊情况。

      Node *match = list->head;
      Node *prev = NULL;
      while( match && (match->id != key) ) {
          prev = match;
          match = match->next;
      }
      

      如果没有匹配,循环将退出,match 将为空。你已经完成了。

      if( !match ) {
          return NULL;
      }
      

      否则我们通过将前一个节点的下一个节点更改为匹配的下一个来删除匹配节点。如果没有前一个节点,我们将头更改为下一个节点。如果匹配最后一个节点,则无害; match->next 将为 null,因此 prev->next 将为 null,列表的新结尾。

      if( prev ) {
          prev->next = match->next;
      }
      else {
          list->head = match->next;
      }
      

      最后,删除的节点怎么办?您可以按原样在函数内free 它。如果您可以完全控制所有节点内存,这是可以接受的。否则,如果节点分配在 stack 上,这可能会导致错误。

      返回已删除的节点并让用户随心所欲地使用它更加灵活。这是从键/值结构中删除时的典型行为:返回值。这就是为什么当我们没有找到任何东西时我会返回上面的NULL

      return match;
      

      至少,返回一个布尔值以指示是否删除了某些内容。

      你可以这样使用它。

      int main() {
          Node node3 = { .id = 3, .next = NULL };
          Node node2 = { .id = 2, .next = &node3 };
          Node node1 = { .id = 1, .next = &node2 };
          List list = { .head = &node1 };
      
          Node *deleted = delete(3, &list);
      
          if( deleted ) {
              printf("Deleted id = %d\n", deleted->id);
          }
          else {
              puts("Node not found");
          }
      
          for(
              Node *node = list.head;
              node;
              node = node->next
          ) {
              printf("id = %d\n", node->id);
          }
      }
      

      我相信你的错误就在这里。

      //Find previous node of the node to be deleted
      while (temp->id != key) {
        temp = temp->next;
      }
      

      这没有找到前一个节点,它找到了匹配的节点。

      此外,free 仅在节点在堆上分配有malloc 时才有效。如果它是在堆栈上分配的,它将无法工作。比如……

      // This is all allocated on the stack an cannot be free()d
      Node node3 = { .id = 3, .next = NULL };
      Node node2 = { .id = 2, .next = &node3 };
      Node node1 = { .id = 1, .next = &node2 };
      List list = { .head = &node1 };
      
      delete(2, &list);
      

      这就是为什么我建议返回已删除的节点并让用户处理它的原因。或者完全控制列表的内存并编写一个处理内存分配的add_node 函数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-11-07
        • 2015-04-25
        • 2019-02-28
        • 1970-01-01
        • 2012-01-26
        • 1970-01-01
        • 1970-01-01
        • 2019-04-14
        相关资源
        最近更新 更多