【问题标题】:How does using node pointer in linked list changes the recursion?在链表中使用节点指针如何改变递归?
【发布时间】:2021-02-01 06:45:10
【问题描述】:

我使用了一个临时节点'nextnode'指向headcur的下一个节点:

SinglyLinkedListNode* removeDuplicates(SinglyLinkedListNode* head) {
    if ( head == NULL ) return NULL;
    SinglyLinkedListNode *nextnode = head->next;
    while ( nextnode != NULL && head->data == nextnode->data ) {
        nextnode = nextnode->next;
    }
    head->next = removeDuplicates( nextnode );
    return head;
} 

这段代码可以正常工作......但是如果我改变了

head->next = removeDuplicates( nextnode );

head->next = removeDuplicates( head->next);

代码输出与输入相同的列表。

但是如果我在所有地方删除nextnode 并使用head->next 代替nextnode,代码就可以正常工作!

实际发生了什么,使它起作用?我错过了什么?

当使用head->nextnextnode 时,这段代码会发生什么?我只是想了解这在内存分配中是如何工作的,以便更好地理解递归。

【问题讨论】:

  • 只需将它画在一张纸上,跟踪nextnodehead->next 在执行过程中的变化。以列表 [1]->[1]->[2]->[2]->[3] 为例。逐步展示您为遵循这些修改所做的工作。
  • 我只知道它在removeduplicate(head->next) 的情况下有效...我不知道它在removeduplicates(nextnode) 的情况下如何工作...在后一种情况下,值头,不改变?或者它是否会改变 nextnode 的值,如果然后它是否转到下一个节点 acc to head->next 或 nextnode->next
  • 在c++中,更喜欢使用nullptr
  • 哦,好吧!这是有道理的..谢谢!

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


【解决方案1】:

以这个列表为例:

head
  ↓
  1 → 2 → 2 → 3 → 3 → 3 → 4

在函数的第一次调用中,nextnode 将指向 [2],由于数据不同 (1 != 2),while 条件将立即为假。所以我们有:

head  nextnode
  ↓   ↓  
  1 → 2 → 2 → 3 → 3 → 3 → 4

然后进行第一次递归调用,在那个执行上下文中,还有另一个head 变量,它被设置为调用者的nextnode 的值。为了更好地跟踪这个head 变量,我将其称为head2,并且当我们在原始调用中谈到变量时将继续引用head

head head2
  ↓   ↓  
  1 → 2 → 2 → 3 → 3 → 3 → 4

创建了一个新的nextnode 变量,这次while 循环将进行一次迭代,因为存在重复值 (2)。当nextnode指向具有3的节点时,迭代停止:

head head2  nextnode
  ↓   ↓       ↓   
  1 → 2 → 2 → 3 → 3 → 3 → 4

进行了另一个递归调用。我们再次得到一个新的head 变量,我将其称为head3

head head2  head3
  ↓   ↓       ↓   
  1 → 2 → 2 → 3 → 3 → 3 → 4

再次,将创建一个新的nextnode。这次它最终指向带有 4 的节点:

head head2  head3       nextnode
  ↓   ↓       ↓           ↓
  1 → 2 → 2 → 3 → 3 → 3 → 4

进行了更深层次的递归调用:

head head2  head3       head4
  ↓   ↓       ↓           ↓
  1 → 2 → 2 → 3 → 3 → 3 → 4

这次新的nextnode 变量将变为NULL,因此我们使用参数NULL 进行递归调用。我们在递归的末尾,因为这个最深的调用将立即返回NULL。我们回到调用的地方,看到head4->next 被分配了这个NULL。在这种情况下,这个分配没有任何区别,因为head4->next 已经是NULL

我们回溯到递归树中的上一层:返回head4。我们确实看到了修改发生。返回的head4 被分配给head3->next,因此值为 3 的第二个和第三个节点都被踢出列表,只需一个分配:

head head2  head3       head4
  ↓   ↓       ↓           ↓
  1 → 2 → 2 → 3 →         4

我们再回溯一步,这次head3被返回分配给head2->next。现在第二个值为 2 的节点被踢出:

head head2  head3   head4
  ↓   ↓       ↓       ↓
  1 → 2 →     3 →     4

再一次回溯:head2 返回分配给head->next。这没什么区别,因为head->next 已经指向head2。顶级函数调用返回head,我们就完成了。

head->next = removeDuplicates( head->next);

这种变化确实会破坏算法。它使nextnode 的值无关紧要。 while 循环现在无济于事,因为代码没有使用 nextnode 的新值。您不妨删除该循环和变量nextnode,然后显然没有删除任何节点。

一个关键的观察是 removeDuplicates 总是返回你作为参数传递给它的节点,所以修改后的语句确实在做:

head->next = head->next

...什么都不做,它是代码中列表链被更改的唯一位置。在其他任何地方都不会更改 next 属性。

如果我在所有地方删除nextnode 并使用head->next 代替nextnode,代码就可以正常工作!

确实如此。在这种情况下,该函数不需要返回任何内容,因为重复删除永远不需要更改列表的head。所以代码看起来像这样:

void removeDuplicates(SinglyLinkedListNode* head) {
  if ( head == NULL ) return;
  while ( head->next != NULL && head->data == head->next->data ) {
    head->next = head->next->next;
  }
  removeDuplicates( head->next );
}

我们可以再次对示例列表进行与此答案顶部相同的分析(请在一张纸上进行),您会得出结论,它也可以有效地删除重复项。不同之处在于,在while 循环中,实际删除一次发生一个节点(每次迭代只删除一个节点),而在原始代码中,它发生在回溯期间,它实际上可以一次删除多个重复节点(正如我们在3 → 3 → 3 → 4 在一个赋值中看到3 → 4 的情况),所以通常原始代码有时可以用更少的赋值完成这项工作,当然当列表中有很多重复值时。

原始代码中的->next 引用也较少。

由于这些差异,理论上原始代码会运行得更快一些。

【讨论】:

    猜你喜欢
    • 2021-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多