二叉树的迭代遍历
上一弹里,我们对如何使用递归算法对二叉树进行遍历做了介绍,写起来非常简单的样子
那么如何使用迭代法求解前序、中序、后序遍历呢?
首先,前序遍历,处理每个结点的顺序应为“根”(指遍历到的当前节点)在最前面,之后跟左孩子和右孩子。
这时候我们会想到一种数据结构,栈。
先把根节点入栈,再入什么呢?是左孩子吗?
如果一棵树只有三个结点[1 2 3]
,如果先入左孩子2,再入右孩子3,那么两个孩子出栈的顺序就会是先出3,再出2,与前序遍历的顺序不一致。
所以当根节点入栈后,要先入右孩子才行。
前序遍历的递归写法如下:
vector<int> preorderTraversal(TreeNode* root) {
if (!root) return {};
stack<TreeNode*> stk;
vector<int> res;
stk.push(root);
while (!stk.empty()) {
TreeNode *node = stk.top();
stk.pop();
res.push_back(node->val);
if (node->right) stk.push(node->right);
if (node->left) stk.push(node->left);
}
return res;
}
其次,中序遍历,处理节点的顺序应为“左根右”,虽然仅仅换了中间结点的顺序,但是却不能按照前序遍历那么写。
原因也很简单,刚刚前序遍历的时候,每次弹出一个节点,就直接把对应的值保存到结果数组中,但是中序遍历却不能如此。
想一想,中序遍历要先遍历到整个树的底层,再逐层向上处理。并非遇到中间结点就要处理中间结点的值。其遍历结点和处理结点的顺序是不一样的。
中序遍历需要先遍历到整棵树的最左下角,依次把途中遇到的所有节点入栈(判断条件就是结点是否为空,如果是空的话就说明已经遍历到最左下角结点的叶子结点了)
之后应该做什么呢?出栈,然后记录数值,然后遍历其右结点。第一次出栈的是左下角结点,其右孩子是空,那么就继续弹出下一个值,该值为最左下角结点的父结点。
到这里整体逻辑就已经初具雏形了,中序遍历的迭代写法,代码如下:
vector<int> inorderTraversal(TreeNode* root) {
if (!root) return {};
stack<TreeNode*> stk;
vector<int> res;
TreeNode *cur = root;
while (cur || !stk.empty()) {
if (cur) {
stk.push(cur);
cur = cur->left;
} else {
cur = stk.top();
stk.pop();
res.push_back(cur->val);
cur = cur->right;
}
}
return res;
}
最后,是后序遍历,顺序是“左右根”,那么写法是不是近似于前序遍历呢?
我们想一想,前序是“根左右”,如果调整一下,变为“根右左”,此时入栈顺序真的就成了先入左孩子,再入右孩子。
将最终结果翻转一下,就成了“左右根”,就是后序遍历的顺序。
所以,后序遍历的迭代写法,代码如下:
vector<int> postorderTraversal(TreeNode* root) {
if (!root) return {};
stack<TreeNode*> stk;
vector<int> res;
stk.push(root);
while (!stk.empty()) {
TreeNode *node = stk.top();
stk.pop();
res.push_back(node->val);
if (node->left) stk.push(node->left);
if (node->right) stk.push(node->right);
}
reverse(res.begin(), res.end());
return res;
}
这样写其实是有些取巧的,而且翻转增添了时间复杂度,那么有没有直接的后序遍历写法呢?
其实也是有的,而是不仅如此,前中后序的迭代法,还有着一种统一的模板,感谢随想录,这部分的内容就放在下一篇了。