C++ 为什么改变链表中下一个元素的指针很重要?

C++ 为什么改变链表中下一个元素的指针很重要?,c++,recursion,linked-list,singly-linked-list,C++,Recursion,Linked List,Singly Linked List,我一直在努力应对黑客银行的挑战。这是一个反向链接列表。我想反复尝试一下,我认为我做得很好。然而,由于一个微小的变化,我的代码无法正常工作: void Reverse(Node *&head) { if (head == NULL) return; Node *link = head->next; if (link == NULL) return; Reverse(link); link->next = head; h

我一直在努力应对黑客银行的挑战。这是一个反向链接列表。我想反复尝试一下,我认为我做得很好。然而,由于一个微小的变化,我的代码无法正常工作:

void Reverse(Node *&head)
{
  if (head == NULL)
      return;

  Node *link = head->next;

  if (link == NULL)
      return;

   Reverse(link);
   link->next = head;
   head->next = NULL;
   head = link;
}
所以这不起作用。我将问题追溯到递归调用
reverse(link)
后的第一行,如果我将该行更改为
head->next->next=head程序通过所有测试。我不理解<代码>链接>下一个< <代码> >代码>头>下一个>下一个< /代码>,如果我们认为<代码>链接< /代码>等于<代码>头>下一个<代码> >strong>为什么哪个指针指向头部很重要?

我的逻辑是:

Given:
--------------------------------------
h     l
|     |
[1]->[2]->[3]
--------------------------------------


link->next = head;
should

 h    l
 |    |
[1]<-[2] [3]


just like

head->next->next = head;

 h   l
 |   |
[1]<-[2] [3]

because head->next-next == link->next
给定:
--------------------------------------
HL
|     |
[1]->[2]->[3]
--------------------------------------
链接->下一步=头部;
应该
HL
|    |
[1] 下一步->下一步=头部;
HL
|   |
[1] 下一步==链接->下一步
我的逻辑不正确吗?我觉得这与我递归调用一个通过引用传递的函数有关。但当我追查出来的时候,我不明白这在这个问题上有什么不同


谢谢

是的-你的逻辑有点错误。更改head的值会破坏算法。这个想法是,在每次返回后,head和link都会恢复它们原来的值

尝试:

但这仍然有一个问题——在名单被撤销后,如何设置新的负责人?您需要捕获指向原始列表中最后一个元素的指针

像这样改变:

void Reverse(Node* p, Node** newHeadAddr)
{
    // Will only happen if list is empty
    if (p == NULL) return;

    Node *link = head->next;

    // If the end is reached
    if (link == NULL) 
    {
         *newHeadAddr = p;
         return;
    }

    Reverse(link, newHead);

    // link now points to last element in reversed list
    // p points to current element
    // so add p to the reversed linked list
    link->next = p;
    p->next = NULL;
}
然后像这样使用它:

Node* newHead = nullptr;
Reverse(head, &newHead);
head = newHead;
关于逻辑:下面是递归调用进行时变量行为的一个示例,即描述p和link在每次调用中指向什么。希望能有帮助

假设您有一个包含三个元素的列表。

第一次打电话时,您有:

First call:
p is a pointer element[0] (i.e. head)
link is a pointer to element[1]
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
Third call:
p is a pointer to element[2]
link is a null pointer
---------------------------------------
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
在第二次通话中,您有:

First call:
p is a pointer element[0] (i.e. head)
link is a pointer to element[1]
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
Third call:
p is a pointer to element[2]
link is a null pointer
---------------------------------------
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
在第三次通话中,您有:

First call:
p is a pointer element[0] (i.e. head)
link is a pointer to element[1]
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
Third call:
p is a pointer to element[2]
link is a null pointer
---------------------------------------
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
由于link为nullptr,因此呼叫将返回,并且您有:

First call:
p is a pointer element[0] (i.e. head)
link is a pointer to element[1]
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
Third call:
p is a pointer to element[2]
link is a null pointer
---------------------------------------
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
Second call:
p is a pointer to element[1]
link is a pointer to element[2]
---------------------------------------
First call:
p is a pointer to element[0] (i.e. head)
link is a pointer to element[1]
现在代码就可以了

link->next = p;     // element[2]->next = element[1]
p->next = nullptr;  // element[1]->next = nullptr;
link->next = p;     // element[1]->next = element[0]
p->next = nullptr;  // element[0]->next = nullptr;
所以它有

element[2]->element[1]->nullptr
然后返回的结果是:

First call:
p is a pointer element[0]
link is a pointer to element[1]
现在代码就可以了

link->next = p;     // element[2]->next = element[1]
p->next = nullptr;  // element[1]->next = nullptr;
link->next = p;     // element[1]->next = element[0]
p->next = nullptr;  // element[0]->next = nullptr;
你有吗

element[2]->element[1]->element[0]->nullptr

你就完成了。

问题是,
反向
不是一个普通的函数。它接受一个引用参数

通常,如果我们有一些变量,比如
x
y
,它们具有相同的值或对象,以及一个函数
f
,那么我们调用
f(x)
还是
f(y)
,都无关紧要

但是,如果
f
实际上不是一个函数,而是一个类似于语法运算符的函数,它可以访问它覆盖的
x
y
位置,那么使用
x
y
可能并不重要。这很重要,因为调用者继续依赖于
x
y
的值,这两个值在调用者的继续执行中具有不同的角色

在(原始)程序中,
link
变量等于
head->next
Reverse(link)
调用之前。但是此调用修改了
链接
,因此在调用之后,关系不再相同。如果需要使用
link
的先前值,则必须使用
head->next

避免混淆的一种方法是对类似于函数的抽象使用清晰的命名,这些抽象会对其参数做一些奇怪的事情,例如破坏它们的值。这适用于引用获取函数以及预处理器宏。例如,如果您看到
increment(x)
,系统会大声提醒您,
x
可能被其后续项覆盖:该
increment
是一个引用获取函数,或者可能是一个预处理器宏。然而,如果您看到
get\u account\u balance(a)
,您可能不会期望它覆盖变量
a
。如果真是这样,那可能是一个难以发现的bug的来源

其次,不用说,您应该避免不必要地使用参考参数。你的函数没有理由有一个递归接口来破坏它的参数

如何在不使用引用参数的情况下对冲销进行编码,是让函数返回冲销列表。如果您仍然需要基于引用的API,可以为此提供一个附加函数:

node *rev_list_destructively(node *list)
{
  if (list == NULL) {
    return NULL; // reversing empty list results in empty list
  } else if (list->next == NULL) {
    return list; // reversing one element list is that list itself
  } else {
    node *rest = list->next;
    node *rest_reversed = rev_list_destructively(rest);

    rest->next = list;
    list->next = NULL;

    return rest_reversed;
  }
}

void rev_list_in_place(node *&list)
{
  list = rev_list_destructively(list);
}
研究递归是如何工作的。如果我们有一个至少包含两个元素的列表(
list->next
不是
NULL
),我们可以调用列表的其余部分
rest
,并将其反转,捕获返回值,从而获得反转的其余部分。我们所要做的就是把头部的节点钉在它的尾部,我们就完成了

我们利用了一个事实,即原始的
rest
变量继续指向它以前指向的同一个节点,而该节点现在位于列表的末尾:因此
rest
指针现在指示尾部节点

尾部节点是为了延伸尾部而需要操纵的节点。我们通过
rest->next=list
来实现这一点。现在,
rest
指向倒数第二个节点,
list
是尾部节点<代码>列表
,当然,一直持续到指向该函数中列表的原始头节点。最后,我们必须使用
list->next=null
以null终止新的尾部节点。当然,我们的返回值是从递归调用中捕获的反向rest列表

例如:

values on entry into recursive case:
  list:  (1 2 3 4)
  rest:  (2 3 4)

after recursion:
  rest_reversed: (4 3 2)
  rest: (2) 
  list: (1 2)  (list->next still points to the (2) node).

after rest->next = list:
  rest_reversed: (4 3 2 1 2 1 2 1 2 1 ...)   -- list is cyclic!!!
  rest: (2 1 2 1 2 1 2 1 2 1 ...)
  list: (1 2 1 2 1 ...)

after list->next = NULL
  rest_reversed: (4 3 2 1) -- the cycle is broken!
  rest: (2 1)
  list: (1)

we return rest_reversed: (4 3 2 1).

逻辑上有一点错误

如果是回溯步骤b-步骤表单
NULL
,它将变为可见

(假设
Head
是引用,只关注递归算法。)

Pop 0:函数返回,上一个函数堆栈继续

Now when it return the control is at

[1] -> [2] -> [3] -> [X] // The function returns to previous call
               ^
现在下一行是

link->next = head; 

i.e. [3]->next = head;
i.e.   [3] -> next = [H]
i.e.   [3] -> [1]

head->next = NULL;

i.e.   [3] -> [1] -> [X]   

head = link

i.e [H]=[3]
Pop 1

现在函数返回