原题连接:java
三道题的解决思路可统一,模板也极其类似,比九章提供的更漂亮。node
好比{1,2,3},当cur位于节点1时,一、2属于“左”节点,3属于“右”节点。DFS的非递归实现本质上是在协调入栈、出栈和访问,三种操做的顺序。上述统一使得咱们再也不须要关注入栈顺序,仅须要关注出栈和访问(第3点),随着更详细的分析,你将更加体会到这种简化带来的好处。git
将对节点的访问定义为results.add(node.val);
,分析以下:github
先序和中序的状况是极其类似的。post
使用上述思路,先序和中序的遍历顺序可统一为:“左”“右”。code
给咱们的直观感受是代码也会比较类似。实际状况正是如此,先序与中序的区别只在于对“左”节点的访问上。对象
不须要入栈,每次遍历到“左”节点,当即输出便可。递归
须要注意的是,遍历到最左下的节点时,实际上输出的已经再也不是实际的根节点,而是实际的左节点。这符合先序的定义。get
while (cur != null) { results.add(cur.val); stack.push(cur); cur = cur.left; }
然后,由于咱们已经访问过全部“左”节点,如今只须要将这些没用的节点出栈,而后转向到“右”节点。因而“右”节点也变成了“左”节点,后续处理同上。it
if (!stack.empty()) { cur = stack.pop(); // 转向 cur = cur.right; }
完整代码以下:
private List<Integer> dfsPreOrder(TreeNode root) { List<Integer> results = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; while (cur != null || !stack.empty()) { while (cur != null) { results.add(cur.val); stack.push(cur); cur = cur.left; } if (!stack.empty()) { cur = stack.pop(); // 转向 cur = cur.right; } } return results; }
基于对先序的分析,先序与中序的区别只在于对“左”节点的处理上
,咱们调整一行代码便可完成中序遍历。
while (cur != null) { stack.push(cur); cur = cur.left; } if (!stack.empty()) { cur = stack.pop(); results.add(cur.val); // 仅调整该行代码 // 转向 cur = cur.right; }
注意,咱们在出栈以后才访问这个节点。由于先序先访问实际根,后访问实际左,而中序刚好相反。相同的是,访问完根+左子树(先序)或左子树+根(中序)后,都须要转向到“右”节点,使“右”节点称为新的“左”节点。
完整代码以下:
private List<Integer> dfsInOrder(TreeNode root) { List<Integer> results = new ArrayList<>(); Stack<TreeNode> stack = new Stack<TreeNode>(); TreeNode cur = root; while (cur != null || !stack.empty()) { while (cur != null) { stack.push(cur); cur = cur.left; } if (!stack.empty()) { cur = stack.pop(); results.add(cur.val); // 转向 cur = cur.right; } } return results; }
后序的状况略有不一样,但仍然十分简洁。
入栈顺序不变,咱们只须要考虑第3点的变化。出栈的对象必定都是“左”节点(“右”节点会在转向后称为“左”节点,而后入栈),也就是实际的左或根;实际的左能够当作左右子树都为null的根,因此咱们只须要分析实际的根。
对于实际的根,须要保证前后访问了左子树、右子树以后,才能访问根。实际的右节点、左节点、根节点都会成为“左”节点入栈,因此咱们只须要在出栈以前,将该节点视做实际的根节点,并检查其右子树是否已被访问便可。若是不存在右子树,或右子树已被访问了,那么能够访问根节点,出栈,并不须要转向;若是尚未访问,就转向,使其“右”节点成为“左”节点,等着它先被访问以后,再来访问根节点。
因此,咱们须要增长一个标志,记录右子树的访问状况。因为访问根节点前,必定先紧挨着访问了其右子树,因此咱们只须要一个标志位。
完整代码以下:
private List<Integer> dfsPostOrder(TreeNode root) { List<Integer> results = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; TreeNode last = null; while(cur != null || !stack.empty()){ while (cur != null) { stack.push(cur); cur = cur.left; } if (!stack.empty()) { cur = stack.peek(); // 若是已经访问了右子树,则能够访问根节点;不然转向,先去访问右子树 if (cur.right == null || cur.right == last) { results.add(cur.val); stack.pop(); last = cur; // 表示已访问过 cur = null; // 表示不须要转向,继续弹栈 } else { // 转向 cur = cur.right; } } } return results; }
思路简洁万岁!模板大法万岁!
消灭人类暴政,世界属于三体!
本文连接:【刷题】二叉树非递归遍历
做者:猴子007
出处:https://monkeysayhi.github.io
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布,欢迎转载,演绎或用于商业目的,可是必须保留本文的署名及连接。