你错过了一个事实,即在一个节点被弹出后,它的右孩子仍然必须被遍历:
current = s.top();
s.pop();
cout << current->data << " ";
current = current->right;
如果堆栈中只有数据,这是不可能的。循环不变式是堆栈恰好包含那些具有未遍历的右子节点的节点。
查看发生了什么的另一种方法是将递归遍历通过代数转换为迭代:
traverse(node) {
if (node) {
traverse(node->left);
visit(node);
traverse(node->right);
}
}
首先将尾调用转换为迭代。我们通过更新参数并将递归调用替换为函数开头的goto 来做到这一点:
traverse(node) {
start:
if (node) {
traverse(node->left);
visit(node);
node = node->right;
goto start;
}
}
goto 和 if 与 while 相同,所以到目前为止
traverse(node) {
while (node) {
traverse(node->left);
visit(node);
node = node->right;
}
}
替换其他递归调用需要我们模拟编译器运行环境的调用栈。我们使用显式堆栈来做到这一点。
traverse(node) {
start:
while (node) {
stack.push(node); // save the value of the argument.
node = node->left; // redefine it the same way the recursive call would have
goto start; // simulate the recursive call
// recursive call was here; it's gone now!
recursive_return: // branch here to simulate return from recursive call
visit(node);
node = node->right;
}
// simulate the recursive return: if stack has args, restore and go to return site
if (!stack.empty()) {
node = stack.pop(); // restore the saved parameter value
goto recursive_return;
}
}
虽然它很丑,但这是一种始终适用于实现递归代码的迭代版本的方法。 (如果有多个非尾递归调用会更复杂,但不会太多。)而且我相信您可以看到与您的代码的相似之处。
我们甚至可以用更多的代数来摆脱丑陋。首先,不难看出这段代码:
start:
while (node) {
stack.push(node); // save the value of the argument.
node = node->left; // redefine it the same way the recursive call would have
goto start; // simulate the recursive call
当以start开头执行时相当于
while (node) {
stack.push(node); // save the value of the argument.
node = node->left; // redefine it the same way the recursive call would have
}
我们也可以替换
if (!stack.empty()) {
node = stack.pop(); // restore the saved parameter value
goto recursive_return;
}
以下
if (!stack.empty()) {
node = stack.pop(); // restore the saved parameter value
visit(node);
node = node->right;
goto start;
}
我们只是将recursive_return: 之后的三个指令复制到if 正文中。
这样的话,就没有办法到达recursive_return这个标签了,所以我们可以把它连同下面两个语句一起删除:
// Dead code! Delete me!
recursive_return:
visit(node);
node = node->right;
我们现在有:
traverse(node) {
start:
while (node) {
stack.push(node); // save the value of the argument.
node = node->left; // redefine it the same way the recursive call would have
}
if (!stack.empty()) {
node = stack.pop(); // restore the saved parameter value
visit(node);
node = node->right;
goto start;
}
}
我们可以通过将最后一个goto start 替换为无限循环来摆脱它:
traverse(node) {
loop {
while (node) {
stack.push(node); // save the value of the argument
node = node->left; // redefine it the same way the recursive call would have
}
if (stack.empty()) break; // original code returns, so does this!
node = stack.pop(); // restore the saved parameter value
visit(node);
node = node->right;
}
}
请注意,我们在与前面代码相同的条件下返回:堆栈为空!
我会让您向自己证明,这段代码的作用与您提供的相同,只是效率更高一些,因为它避免了一些比较!我们根本不需要对指针和堆栈元素进行推理。它“刚刚发生”。