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
现在函数返回