我对此代码感到困惑:
void in_order_traversal_iterative(BinaryTree *root) {
stack<BinaryTree*> s;
BinaryTree *current = root;
while (!s.empty() || current) {
if (current) {
s.push(current);
current = current->left;
} else {
current = s.top();
s.pop();
cout << current->data << " ";
current = current->right;
}
}
}
我们设置一个指向root的指针。然后,如果它存在,则将当前(当前为root)推入堆栈。我不明白为什么我们最初将整个树推入堆栈,而不仅仅是节点所拥有的数据的值。我是否完全错过了某些内容或者不理解为什么它会以这种方式工作?我无法理解为什么我们将整棵树推入,而不是单个节点......
答案 0 :(得分:4)
您错过了这样一个事实,即在弹出一个节点后,仍然必须遍历其正确的子节点:
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;
}
}
请注意,我们在与前一代码相同的条件下返回:堆栈为空!
我会让你向自己证明这段代码和你提供的代码一样,只是它更有效率,因为它避免了一些比较!我们根本不需要对指针和堆栈元素进行推理。它“刚刚发生。”
答案 1 :(得分:1)
它不会将整棵树推入堆栈,它会推动树的最左边部分。然后它开始弹出元素并按升序推动他们最右边的对手。