C++ 下面递归程序的最后一行是如何工作的?
我在研究链表,然后我发现了一个递归反转链表的代码。这是C++代码。< /P>C++ 下面递归程序的最后一行是如何工作的?,c++,recursion,linked-list,reverse,singly-linked-list,C++,Recursion,Linked List,Reverse,Singly Linked List,我在研究链表,然后我发现了一个递归反转链表的代码。这是C++代码。< /P> void recursiveReverse(node*& head) { node* first; node* rest; /* checking for an empty list */ if (head == NULL) return; first = head; rest = first->next; /* List has only one node *
void recursiveReverse(node*& head)
{
node* first;
node* rest;
/* checking for an empty list */
if (head == NULL)
return;
first = head;
rest = first->next;
/* List has only one node */
if (rest == NULL)
return;
recursiveReverse(rest);
first->next->next = first;
first->next = NULL;
/* fix the head pointer */
head = rest;
}
除了最后一行,我都懂了。因为据我所知,rest指针在递归展开期间也会像第一个指针一样更新,因此,在代码末尾,头不会指向最后一个节点
下面是我对以下代码的解释
让我们看一个链表1->2->3
最初,first将存储head节点和rest的地址
将包含节点2的地址。
现在,由于rest不是NULL,因此将调用RecursiveEversest。
现在,第一个将指向节点2,其余的将指向节点3。
同样,rest不是空的,因此将调用RecursiveEverserest。
现在,第一个将指向节点3,其余将包含NULL。
之后,递归将开始展开,首先返回节点2,其余将返回节点3。
现在,语句first->next->next=first;将导致节点3的下一部分指向节点2,链表将变为1->2 next->next=first;将导致节点2的下一部分指向节点1,链表将变为1可能还有其他误解,但我假设基本的误解与您的解释有关语句head=rest将导致head指向节点2而不是节点3,因为第一个->下一个的其余部分当前位于节点2 最后,head将指向初始列表的最后一个节点。让我们考虑一下代码的简化/缩短部分:
rest = head->next;
if (rest == NULL) // end of list reached; head points tho the last node
return;
recursiveReverse(rest); // if the end is not reached, go forward with the next node (i.e. the value of head->next
head = rest; // reset head to the (shared) value of rest.
这是因为递归Everserest语句将被一次又一次地调用,直到head->next为NULL,即到达列表的末尾。RecursiveVerse的最后一次运行返回,因为head->next==NULL,在调用者中,变量rest指向最后一个节点。
现在请注意,rest在所有对RecursiveVerse的调用中共享,因为它是通过引用传递的。因此,到目前为止调用的每个RecursiveVerse实例都将调用语句head=rest,但是-由于rest在所有调用中共享,并且在递归调用后不会更改-语句head=rest将始终将head分配给相同的rest值,该值仍然指向最后一个节点
希望这是全面的
无论如何,使用引用传递的参数执行递归函数通常很难理解;通常,当递归函数管理其私有状态,但使用返回值来协调结果时,事情会变得更容易。如果对代码进行排列,使node*reverseRecursivenode*处于当前状态,则代码将更易于理解。如步骤8所示。您忘记了head in void RecursiveVersenode*&head是一个引用。所以当你递归地调用递归珠穆朗玛峰时;然后通过引用传递rest。这意味着,在递归内部,head是调用函数中对rest变量的引用。因此,当它在递归中被更改为指向3时,调用函数中的rest也被更改 如果这听起来令人困惑,那么在堆栈上绘制局部变量可能会有所帮助: 1最初,head将是指向传递给函数的节点的指针的引用,首先将存储head节点的地址,其余将包含节点2的地址 堆栈看起来像这样,只显示局部变量,忽略调用函数时已经存在的任何变量:
====================================
head: reference to original head
first: node 1
rest: node 2
现在,由于rest不是空的,所以将调用RecursiveEversest
3现在,head将在调用函数中引用rest,首先将指向节点2,rest将指向节点3。
堆栈将如下所示:
====================================
head: reference to original head
first: node 1
rest: node 2 <---------------+
==================================== |
head: reference to ---------------+
first: node 2
rest: node 3
====================================
head: reference to original head
first: node 1
rest: node 2 <---------------+
==================================== |
head: reference to ---------------+
first: node 2
rest: node 3 <---------------+
==================================== |
head: reference to ---------------+
first: node 3
rest: NULL
6由于rest为空,我们只返回,堆栈返回到:
====================================
head: reference to original head
first: node 1
rest: node 2 <---------------+
==================================== |
head: reference to ---------------+
first: node 2
rest: node 3
8之后,返回并首先再次指向节点1,语句first->next->next=first;将导致节点2的下一部分指向节点1,链接列表将变为1我们初学者应该互相帮助: 在我看来,对于像我们这样的初学者来说,要理解函数是如何工作的并不容易 因此,最好使用方案来计算它的工作 如果列表不包含节点或仅包含一个节点,则没有可反转的内容 此代码段对应于此结论
/* checking for an empty list */
if (head == NULL)
return;
first = head;
rest = first->next;
/* List has only one node */
if (rest == NULL)
return;
现在让我们假设列表正好包含两个节点。在这种情况下,它看起来像
-------- ---------- ----------------
| head | -> | A |B| -> | B |nullptr|
-------- ---------- ----------------
此列表按以下方式分为以下部分
-------- ----------
| head | -> | A |B|
-------- ----------
-------- ----------
| first| -> | A |B|
-------- ----------
-------- ----------------
| rest | -> | B |nullptr|
-------- ----------------
此代码段对应于此结论
first = head;
rest = first->next;
head = rest;
现在函数递归地调用自己
recursiveReverse(rest);
事实上,这个新的列表需要它
-------- ----------------
| rest | -> | B |nullptr|
-------- ----------------
由于这个新列表只包含一个节点,因此函数只返回
由于原始列表必须反转,因此头部必须包含rest值,即头部必须poi
nt到节点B
此代码段对应于此结论
first = head;
rest = first->next;
head = rest;
然而,在这种情况下,我们将得到
-------- ----------------
| head | -> | B |nullptr|
-------- ----------------
-------- ---------------- | -----------
| first | -> | A |nullptr| | | B | A|-----------
-------- ---------------- | ----------- |
^ V
-------- | -----------------
| rest | ---------------------------- | A | nullptr|
-------- -----------------
-------- ---------------- | -----------
| first | -> | A |nullptr| | | B | A|-----------
-------- ---------------- | ----------- |
^ V
-------- | -----------------
| head | ---------------------------- | A | nullptr|
-------- -----------------
但是这个列表只包含一个节点B
head = rest;
first->next = NULL;
head = rest;
我们需要在列表中添加节点A
正如我们所看到的,指针首先指向节点A
然后我们可以做下面的事情
first->next->next = first;
这导致
-------- ---------- -----------
| first | -> | A |B| -> | B | A|
-------- ---------- -----------
这是另一方面,我们有
-------- ---------- -----------
| rest | -> | B |A| -> | A | B|
-------- ---------- -----------
然后在这个声明之后
head = rest;
first->next = NULL;
head = rest;
我们会得到
-------- ----------------
| head | -> | B |nullptr|
-------- ----------------
-------- ---------------- | -----------
| first | -> | A |nullptr| | | B | A|-----------
-------- ---------------- | ----------- |
^ V
-------- | -----------------
| rest | ---------------------------- | A | nullptr|
-------- -----------------
-------- ---------------- | -----------
| first | -> | A |nullptr| | | B | A|-----------
-------- ---------------- | ----------- |
^ V
-------- | -----------------
| head | ---------------------------- | A | nullptr|
-------- -----------------
现在确实是时候发表声明了
head = rest;
first->next = NULL;
head = rest;
我们会得到
-------- ----------------
| head | -> | B |nullptr|
-------- ----------------
-------- ---------------- | -----------
| first | -> | A |nullptr| | | B | A|-----------
-------- ---------------- | ----------- |
^ V
-------- | -----------------
| rest | ---------------------------- | A | nullptr|
-------- -----------------
-------- ---------------- | -----------
| first | -> | A |nullptr| | | B | A|-----------
-------- ---------------- | ----------- |
^ V
-------- | -----------------
| head | ---------------------------- | A | nullptr|
-------- -----------------
也就是说,列表是相反的
如果列表包含两个以上的节点,则在拆分原始列表后,指针将首先指向原始列表的第一个节点,该节点必须是反向列表中的最后一个节点。反过来,第一个节点将指向反向列表中的下一个节点,该节点将是最后一个节点
使用此代码段
first->next->next = first;
first->next = NULL;
我们可以将其放置在反转列表中的最后一个节点之后。我们所需要做的就是将head设置为指针rest中存储的值,因为指针rest是反向列表的头,指针指向的节点首先被附加到反向列表中
head = rest;
仅此而已
这是一个演示程序
#include <iostream>
struct node
{
int value;
node *next;
};
void push( node * &head, int value )
{
head = new node { value, head };
}
std::ostream & out( node * const &head, std::ostream &os = std::cout )
{
for ( node *current = head; current != nullptr; current = current->next )
{
os << current->value << ' ';
}
return os;
}
void recursiveReverse( node * &head )
{
if ( head != nullptr && head->next != nullptr )
{
node *first = head;
node *rest = head->next;
recursiveReverse( rest );
first->next->next = first;
first->next = nullptr;
head = rest;
}
}
int main()
{
node *head = nullptr;
const int N = 10;
for ( int value = 0; value < N; value++ )
{
push( head, value );
}
out( head ) << std::endl;
recursiveReverse( head );
out( head ) << std::endl;
return 0;
}
你可以用调试器一行一行地调试,找出真正发生的事情。@OliverCharlesworth,我一行一行地调试,我知道rest没有改变,但它是固定的。但我无法找出它发生的原因,这就是为什么,我在这里提出了这个问题。试着在函数调用之前在纸上写下列表的状态,每次列表的状态都会改变,同时逐行进行。