一、分清前、中、后序遍历
看根节点遍历的顺序,左子节点一定在右子节点之前遍历 :
- 前序 : 根左右
- 中序 : 左根右
- 后序 : 左右根
二、前序遍历
递归写法 : 直接看代码 :
迭代算法 :
众所周知,把递归改成迭代就需要手动去维护一个栈,关键就在于理清栈的入栈出栈顺序。首先我们一定要搞清楚一点,栈是先进慢出的,因此,如果我们想要先遍历左子节点,就要先把右子节点入栈。
前序遍历的顺序是:根左右。当我们出栈时,得到的元素就是一个根节点 (记为node)。然后我们先把node的右子节点入栈,再把左子节点入栈,这样等到出栈的时候就是先出左子节点、再出右子节点,符合前序遍历的顺序。
当理不清出入栈顺序时,手动画一个栈模拟一下就能清楚。
三、中序遍历
递归写法:与前序遍历的写法类似,这里就不写了。
迭代写法:
(1) 前序遍历时,访问的第一个节点就是我们要处理的第一个节点,而中序遍历不一样,我们访问的第一个节点 (根节点)并不是我们要处理的第一个节点 (最左的节点)。事实上,我们访问到的每一个节点都是中间节点。因此,在处理节点之前我们需要先找到这个“最左”的节点,这需要一次循环,不断向左走,边走边把遇到的节点都入栈。
(2)在我们出栈时,也就是执行root = myStack.pollLast();
这条代码时候,root.left这棵树已经完全遍历完了,因此我们此刻访问的是中间节点,访问完中间节点之后进行赋值root = root.right
,目的是在下次循环的时候完成root.right这棵树的遍历。
(3) 最外层循环的循环条件:①首先,root!=null这点跟递归写法的意义类似。② !myStack.isEmpty(),栈非空代表还有一些节点没被遍历到。
(4) 与递归写法联系一下:
参考资料:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/dai-ma-sui-xiang-lu-che-di-chi-tou-qian-xjof1/
四、后序遍历
递归写法:与前序遍历的写法类似,这里就不写了。
迭代写法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Deque<TreeNode> stk = new LinkedList<>();
TreeNode prev = null;
//循环的时候牢记:root代表当前访问的节点;栈stk中的是还没有被访问的节点
//此处的判断条件是:当前访问的不是空节点 && 栈中还有节点没被访问到
while(root != null || !stk.isEmpty()){
//这个while循环及出栈操作的目的是:找到最左的节点
while(root != null){
stk.offerLast(root);
root = root.left;
}
root = stk.pollLast();
//判断当前访问的这个节点(root)是否可以被放进后序序列中
//因为后序序列是:左右根,所以必须是右节点访问过了才能访问root
if(root.right == null || root.right == prev){
res.add(root.val);
prev = root;
root = null;
}else{
//如果root.right还没被访问过,那么root此时还不能被放入后序序列中,因此把它入栈
//入栈之后把root赋值为root.right,这是因为下次遍历要从root.right开始
stk.offerLast(root);
root = root.right;
}
}
return res;
}
}
这里注意一下root = null
这句代码,因为后序遍历的顺序是:左右根。因此当处理完根节点之后,需要把root置null,这样下次进来循环的时候才会从栈中弹出节点;否则就会继续向左搜索,陷入死循环。
也可以这样理解:当我们访问完根节点root的时候,说明root.left已经全部访问完了(根据后序遍历的定义),因此我们不希望下次遍历会继续往root的左边去了,所以内循环的这个while循环是没有意义的,因此我们把root置为null,避免再次进入内循环。
参考资料:https://leetcode-cn.com/problems/binary-tree-postorder-traversal/solution/er-cha-shu-de-hou-xu-bian-li-by-leetcode-solution/