二叉树刷题篇前中后序遍历的递归与迭代算法
Posted 归宅观察部
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树刷题篇前中后序遍历的递归与迭代算法相关的知识,希望对你有一定的参考价值。
首先是经典的前序、中序、后序遍历
前序:根->左->右
中序:左->根->右
后序:左->右->根
其实这里有个记忆的小技巧,就是用这三个节点画出一个箭头,前序是向前的箭头,中序是向上的箭头,后序是向后的箭头,这样就非常直观了。(画图太麻烦我就不放了哈哈)
三种遍历的递归写法都是类似的,十分方便,下面给出中序遍历的代码,前序、后序遍历只需微小的改动。
有两个很小的点值得注意,一个是根节点为NULL的情况,result会直接返回[],这说明vector无参数的声明会生成一个空的向量。
另一个是在这道题中,利用另外的函数,并使用引用传参会省事很多。
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
对于前、中、后序遍历来说,相对困难的是其迭代写法。那么迭代写法该如何实现呢?
首先可以想到的是,递归的底层原理就是将每一次的参数和待处理的数据压入栈中,那么递归可以做的,栈也可以做!
前序遍历(迭代写法):
先捋一下思路,如果想用迭代实现二叉树的前序遍历,就是要得到根-左-右这样一个顺序的数组,如果使用栈,就要按照相反的顺序将数据压入栈中,那么以下面的树为例
相反的顺序 是6-4-2-1-5?很显然错了 前序遍历的正确结果是5-4-1-2-6
如果从结果反推压栈的顺序,即6-2-1-4-5,好像能够得到一定的规律?错,这样的所谓“压栈”不过是将之前得到的答案反过来推一遍,并不是真正的迭代,而是一种逆的递归。
那么真正的迭代是什么样的呢?应该是处理完的节点便不再处理,这里写的有点乱,但能够帮助我们理解递归与迭代的区别。
回到这个树,我们肯定要先去处理5这个节点,压栈,出栈,然后是他的右孩子和左孩子(这个顺序用到了栈的特性,即先进后出),所以接下来是6,将6压入栈中不出栈,4压入栈中,出栈,2压入栈中不出栈,1压入栈中出栈,2出栈,6出栈。(开始念经;))
这是怎么规定的?看起来很迷惑。让我们直接快进到答案吧(x)(这样可不是投机取巧,有时候我们确实要看现成的代码才能学习别人的思路!)
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
if (node != NULL) result.push_back(node->val);
else continue;
st.push(node->right); // 右
st.push(node->left); // 左
}
return result;
}
};
//本代码来自公众号 代码随想录 作者为Carl
可以看到,这里代码的思路是先处理节点5,然后用node去存储已经被处理完的节点(因为这个节点会被出栈,但我们还需要知道它的左右孩子),然后再将它的右、左孩子先后压入栈中,可以得知,这里在一个节点没有左右孩子的情况下,会压入一个空指针,所以需要判断(node!=NULL)这个条件。
另一方面,一开始我们的思路就在错误的方向上一路狂奔……因为我们陷入了一种思维定式,认为迭代的算法仍然要用int类型的栈去压一个又一个节点的值,这与迭代的思维是相违背的,正确的思路应该是像前文代码一样,将节点压入栈,然后用去处理栈中的节点,这才是迭代的思维。
好,这些话依然说得云里雾里(我自己可能还能懂一点,如果你不幸在读这篇文章,估计是完全看不懂了hhhh),虽然前序遍历的代码看懂了,但中序和后序呢?恐怕还是没那么容易。
回顾中序遍历的顺序:左->根->右
如法炮制,我们先将5的右孩子6压入栈中,然后是5压入栈中。然后是4,显然4的子节点就没有子节点了,得1-4-2,这时处理5,再处理6,得1-4-2-5-6.发挥魔改大法,看看能不能直接从上文代码改一个出来。试了下,似乎不行……好,只能再看代码了:(
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != NULL || !st.empty()) {
if (cur != NULL) { // 指针来访问节点,访问到最底层
st.push(cur); // 讲访问的节点放进栈
cur = cur->left; // 左
} else {
cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
st.pop();
result.push_back(cur->val); // 中
cur = cur->right; // 右
}
}
return result;
}
};
芜湖,新的一天,新的BUG~早起又自己写了一下中序遍历的迭代算法,两点小问题,一个是stack的声明中TreeNode要加*,因为是存指针,另一点就是cur=st.top()后,要pop一下。
还有就是昨晚有些晚了,导致代码随想录的介绍都没怎么看进去,再次重复一遍算是明白了为什么前序与中序遍历的代码差别并不小,即在前序遍历中,指针访问的顺序就是访问元素的顺序,在中序遍历中,这两个顺序是不相同的,因此需要指针访问数据,栈处理数据。
好,中序遍历结束,我们来看后序遍历。
后序遍历——左,右,根。如果从头去想的话还是很困难的,但是和前序遍历对比一下,就可以发现只要简单的修改就可以实现后序遍历。前序遍历是根-左-右。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
if (node != NULL) result.push_back(node->val);
else continue;
st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序
st.push(node->right);
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
至此,二叉树前、中、后序遍历的迭代写法都已经写完了!
以上是关于二叉树刷题篇前中后序遍历的递归与迭代算法的主要内容,如果未能解决你的问题,请参考以下文章