我们知道,在二叉树的三种遍历方式中,非递归的后续遍历是最难的,但是一个偶然的发现让我觉得二叉树的后序遍历也没有那么难。发生这样转换的关键在于反向思维,具体讲解如下。
以下面的二叉树为例讲解这个问题。
有二叉树如上所示,其前序遍历为:
1 2 4 7 3 5 6 8
其后序遍历为:
7 4 2 5 8 6 3 1
如果我们将后续遍历反过来看(暂时称为反后序遍历),其遍历的顺序为:
1 3 6 8 5 2 4 7
看出来什么问题没有?其实反后序遍历和前序遍历是一样的,唯一的区别是,前序遍历的第二个位置是左节点,而反后序遍历的第二个位置是右节点。还是简单画个图:
对于上面的简单二叉树,前序遍历是:a b c,反后序遍历是:a c b。
有了这一个层面的理解后,我们就可以更“简洁”地实现后续遍历了:1 获取反后序遍历;2 翻转反后序遍历。
而什么说这样简单呢?基于两点原因:1 前序遍历的非递归实现是比较容易的;2 翻转一个序列也是比较容易的。
好吧,说到这里,可以直接上代码了。对于前序遍历的非递归实现,可以这样写:
int PreorderTraverseIteratively(BinaryTreeNode* root) {
stack<BinaryTreeNode*> node_stack;
node_stack.push(root);
while (!node_stack.empty()) {
BinaryTreeNode* node = node_stack.top();
node_stack.pop();
if (node == nullptr) continue;
cout << "preorcer iteratively: " << node->value << endl;
node_stack.push(node->right);
node_stack.push(node->left);
}
return RET_SUCCESS;
}
而反后序遍历与前序遍历的区别就是的输出的顺序需要变化;对于翻转,为了使代码显得简洁,我们使用栈来实现这个操作,最终的代码如下:
int PostorderTraverseIterativelyV2(BinaryTreeNode* root) {
stack<BinaryTreeNode*> node_stack;
stack<int> output_stack;
node_stack.push(root);
while (!node_stack.empty()) {
BinaryTreeNode* node = node_stack.top();
node_stack.pop();
if (node == nullptr) continue;
output_stack.push(node->value);
node_stack.push(node->left);
node_stack.push(node->right);
}
while (!output_stack.empty()) {
cout << "postorder iteratively v2: " << output_stack.top() << endl;
output_stack.pop();
}
return RET_SUCCESS;
}
瞬间感觉比之前对后序遍历的非递归方式的理解清晰了不少,哈哈哈。