以下递归程序的最后一行是如何工作的
How the last line of following recursive program is working?
我正在研究链表,然后我发现了一个递归反转链表的代码。这是C++代码。
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;
}
我理解了除最后一行之外的整个代码。因为根据我的说法,在递归展开期间,其余指针的更新也与第一个指针类似,因此,在此代码结束时,头部不会指向最后一个节点。
这是我对以下代码的解释。
让我们以链表 1 -> 2 -> 3 为例。
- 最初,首先将存储头节点的地址,其余部分 将包含节点 2 的地址。
- 现在,由于 rest 不是 NULL,因此,将调用递归反向(rest)。 现在,第一个将指向节点
- 2,其余将指向节点 3。
- 同样,rest不是NULL,因此将调用递归反向(rest)。
- 现在,第一个将指向节点 3,其余将包含 NULL。
- 之后,递归将开始展开,首先将返回到节点 2,其余部分将返回到节点 3。
- 现在,语句 first -> next -> next = first;将导致节点 3 的下一部分指向节点 2,链表将变为1 -> 2 <- 3。节点 2 的下一部分将包含 NULL,并且由于 head = rest,因此,head 也将指向最后一个节点作为rest指针。 之后,first 将指向节点 1,
- 语句first -> next -> next = first;将导致节点 2 的下一部分指向节点 1,链表将变为 1 <-2 <- 3。节点 1 的下一部分将包含 NULL,语句head = rest将导致头部指向节点 2 而不是节点 3,因为其余部分(第一个 -> 下一个)当前位于节点 2。
任何人都可以解释一下我在解释此代码时的错误之处吗?
也许还有其他误解,但我假设基本的误解与您的解释有关"语句 head = rest 将导致头部指向节点 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.
这是因为语句recursiveReverse(rest)
会被一次又一次地调用,直到head->next
被NULL
,即到达列表的末尾。recursiveReverse
的最后一次运行返回,因为head->next == NULL
,在调用者中,变量rest
指向最后一个节点。 现在请注意,"rest"在所有对recursiveReverse
的调用中共享,因为它是通过引用传递的。因此,到目前为止调用的每个recursiveReverse
实例都将调用语句head = rest
,但是 - 由于rest
在所有调用之间共享并且在递归调用后不会更改 - 语句head = rest
将始终将head
分配给相同的值rest
,该值仍然是指向最后一个节点的值。
Puh - 希望这是全面的。
无论如何,使用通过引用传递的参数执行递归函数通常很难理解;通常,当递归函数管理其私有状态但使用返回值来协调结果时,事情会变得更容易。如果你把代码安排成你有node* reverseRecursive(node *current)
,你的代码会变得更容易理解。
它看起来像在步骤 8 中。 您忘记了void recursiveReverse(node*& head)
中的head
是参考。因此,当您递归调用recursiveReverse(rest);
时,rest
是通过引用传递的。这意味着,在递归内部,head
是对调用函数中rest
变量的引用。因此,当它被更改为指向递归内部的3
时,调用函数中的rest
也发生了变化。
如果这听起来令人困惑,那么在堆栈上绘制局部变量可能会有所帮助:
1) 最初,head
将是指向传递给函数的节点的指针的引用,first
将存储头节点的地址,rest
将包含节点 2 的地址。
堆栈将如下所示(仅显示局部变量并忽略调用函数时已经存在的任何变量):
====================================
head: reference to original head
first: node 1
rest: node 2
2)现在,既然休息不NULL
,recursiveReverse(rest)
就会被召唤。
3)现在,head
将在调用函数中引用rest
,first
将指向节点2,rest
将指向节点3。 堆栈将如下所示:
====================================
head: reference to original head
first: node 1
rest: node 2 <---------------+
==================================== |
head: reference to ---------------+
first: node 2
rest: node 3
4)同样,休息不是NULL
,因此recursiveReverse(rest)
将被召唤。
5)现在,head
将在最后一个调用函数中引用rest
,first
将指向节点3,rest
将包含NULL
。 堆栈将如下所示:
====================================
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
NULL
我们只是返回,堆栈回到这个:
====================================
head: reference to original head
first: node 1
rest: node 2 <---------------+
==================================== |
head: reference to ---------------+
first: node 2
rest: node 3
7)现在,语句first->next->next = first;
将导致节点3的next
部分指向节点2,链表将变得1 -> 2 <- 3
。节点 2 的下一部分将包含NULL
。由于head
是对调用函数中rest
的引用,因此head = rest
将使head
引用的rest
指向与本地rest
相同的节点:
====================================
head: reference to original head
first: node 1
rest: node 3 <---------------+ (head = rest; made *this* rest
==================================== | be equal to the *local* rest)
head: reference to ---------------+
first: node 2
rest: node 3
8)之后,我们返回,first
将再次指向节点1,语句first->next->next = first;
将导致节点2的下一部分指向节点1,链表将变得1 <- 2 <- 3
。节点 1 的下一部分将包含NULL
,语句head = rest
将导致head
引用的指针指向节点 3,因为rest
在步骤 7 中更改为指向节点 3。
我们初学者应该互相帮助:)
在我看来,对于像我们这样的初学者来说,理解该功能是如何工作的并不容易。
因此,最好使用方案来弄清楚它的工作。
如果列表不包含节点或仅包含一个节点,则没有什么可以逆转的。
此代码片段对应于此结论。
/* 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;
现在函数递归地调用自己
recursiveReverse(rest);
事实上,这个新列表需要
-------- ----------------
| rest | -> | B |nullptr|
-------- ----------------
由于这个新列表只包含一个节点,因此该函数只返回。
由于原始列表必须反转,因此head
必须包含rest
的值,即head
必须指向节点"B"
此代码片段对应于此结论
head = rest;
但是在这种情况下,我们将得到
-------- ----------------
| head | -> | B |nullptr|
-------- ----------------
但是此列表仅包含一个节点"B"
。所以在执行此语句之前
head = rest;
我们需要将列表与节点"A"
一起附加。
正如我们所看到的那样,指针first
指向节点"A"
-------- ----------
| first| -> | A |B|
-------- ----------
那么我们可以执行以下操作
first->next->next = first;
这导致
-------- ---------- -----------
| first | -> | A |B| -> | B | A|
-------- ---------- -----------
另一方面,我们有
-------- ---------- -----------
| rest | -> | B |A| -> | A | B|
-------- ---------- -----------
然后在此声明之后
first->next = NULL;
我们会得到
-------- ---------------- | -----------
| first | -> | A |nullptr| | | B | A|-----------
-------- ---------------- | ----------- |
^ V
-------- | -----------------
| rest | ---------------------------- | A | nullptr|
-------- -----------------
现在确实是时候发表声明了
head = rest;
我们会得到
-------- ---------------- | -----------
| first | -> | A |nullptr| | | B | A|-----------
-------- ---------------- | ----------- |
^ V
-------- | -----------------
| head | ---------------------------- | A | nullptr|
-------- -----------------
那就是列表颠倒了。
如果列表包含两个以上的节点,则在拆分原始列表后,指针first
将指向原始列表的第一个节点,该节点必须是反向列表中的最后一个节点。反过来,第一个节点将指向下一个节点,该节点在反向列表中将是最后一个节点。
使用此代码片段
first->next->next = first;
first->next = NULL;
我们可以将其放在反向列表中的最后一个节点之后。我们需要做的就是将head
设置为存储在指针rest
中的值,因为指针rest
是反向列表的头部,指针first
指向的节点附加到该列表。
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;
}
它的输出是
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
- QSqlquery prepare()和bindvalue()不工作
- 用c++从输入文件中读取另一行
- 读取文件的最后一行并输入到链接列表时出错
- 导入库可以跨dll版本工作吗
- 我正在使用嵌套的while循环来解析具有多行的文本文件,但由于某种原因,它只通过第一行,我不知道为什么
- 从C++dll访问C#中的一行主要参数
- 以螺旋方式打印矩阵的程序.(工作不好)
- 对象指针在c++中是如何工作的
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- VSOMEIP-2个设备之间的通信(TCP/UDP)不工作
- 为字符串中每 N 个字符插入空格的函数没有按照我认为的方式工作?
- 在C++中,我如何接受不同于同一行的用户输入
- C++为线程工作动态地分割例程
- 为什么我的 std::ref 无法按预期工作?
- 布尔比较运算符是如何在C++中工作的
- SampleConsensusPrerejective(ext.RANSAC)是如何真正工作的
- 以下递归程序的最后一行是如何工作的
- C :SETW()仅在第一行,循环中工作
- 有人可以解释一下工会在这一行代码中是如何工作的,以及数字是如何交换的
- 在C++中,同一行中的预增量和后增量是如何工作的