一、递归算法
递归算法就很简单了,有固定的模板,前中后序万变不离其宗,代码如下(只在注释处有改动)。
-
前序遍历:
class Solution { List<Integer> res = new ArrayList<>(); public List<Integer> preorderTraversal(TreeNode root) { if(root == null) return res; dfs(root); return res; } public void dfs(TreeNode root){ if(root == null) return; res.add(root.val); //操作根节点 dfs(root.left); //递归左子树 dfs(root.right); //递归右子树 return; } }
-
中序遍历:
class Solution { List<Integer> res = new ArrayList<>(); public List<Integer> inorderTraversal(TreeNode root) { if(root == null) return res; dfs(root); return res; } public void dfs(TreeNode root){ if(root == null) return; dfs(root.left); //递归左子树 res.add(root.val); //操作根节点 dfs(root.right); //递归右子树 return; } }
-
后序遍历:
class Solution { List<Integer> res = new ArrayList<>(); public List<Integer> postorderTraversal(TreeNode root) { if(root == null) return res; dfs(root); return res; } public void dfs(TreeNode root){ if(root == null) return; dfs(root.left); //递归左子树 dfs(root.right); //递归右子树 res.add(root.val); //操作根节点 return; } }
二、迭代算法
虽然递归方法比较好理解,写起来也很方便,但是仅仅掌握递归算法是不够的,在面试中面试官经常会要求我们用非递归的方式实现二叉树的遍历,所以二叉树遍历的迭代算法也是必须掌握的。
以下提供两种思路来实现迭代算法,一种为老思路,没有固定的模板,三种遍历方式各不相同,理解也记忆起来比较麻烦;而新思路提供了一种固定模板实现迭代算法,理解一个其他两个就都迎刃而解了。
(1)老思路
-
前序遍历:
思路:维护一个栈,在每个根节点,先对根节点进行操作,然后将根节点的右节点压入栈中,再不断遍历根的左节点。当左子树遍历完。再取栈中元素即右子树节点,进行遍历。
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); if(root == null) return res; while(root != null || !stack.isEmpty()){ while(root != null){ res.add(root.val); //操作根节点 stack.push(root.right); //将右节点压栈 root = root.left; //循环遍历左节点 } root = stack.pop(); //左子树遍历完了再来遍历栈中的元素 } return res; } }
-
中序遍历
思路:维护一个栈,不断遍历左节点,将左节点作为根节点压入栈中。当左节点遍历完毕,从栈中取元素作为根节点进行操作,再遍历根节点的右子树。
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); if(root == null) return res; while(root != null || !stack.isEmpty()){ while(root != null){ stack.push(root); //将左节点作为根节点压入栈中 root = root.left; //不断遍历左节点 } root = stack.pop(); //左子树遍历完了,取栈中元素作为根节点 res.add(root.val); //操作根节点 root = root.right; //遍历根节点的右子树 } return res; } }
-
后序遍历:
思路:后序遍历可以参考前序遍历,前序遍历的节点访问顺序为:根–>左–>右,而后序遍历的节点访问顺序为:左–>右–>根,如果将后续遍历的结果反转,则为:根–>右–>左,可以对前序遍历的方法进行调整。
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); if(root == null) return res; while(root != null || !stack.isEmpty()){ while(root != null){ res.add(root.val); //操作根节点 stack.push(root.left); //将左节点压栈 root = root.right; //循环遍历右节点 } root = stack.pop(); //右子树遍历完了再来遍历栈中的元素 } Collections.reverse(res); //对集合进行反转 return res; } }
(2)新思路
参考Leetcode上的题解,大佬总结了一种新的思路,即递归的本质就是压栈,按照递归的思路来进行迭代。这样可以得到一个统一的模板(只在if判断体中有不同)。
这里只分享代码,如果需要进一步深入理解请参考原文。
-
前序遍历:
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); if(root != null) stack.push(root); while(!stack.isEmpty()){ TreeNode tmp = stack.pop(); if(tmp != null){ //如果节点不为空,表明根节点没被访问过 if(tmp.right != null) stack.push(tmp.right); //右节点入栈 if(tmp.left != null) stack.push(tmp.left); //左节点入栈 stack.push(tmp); //根节点入栈 stack.push(null); //入栈空节点 }else{ //如果节点为空,表明根节点被访问过 res.add(stack.pop().val); } } return res; } }
-
中序遍历:
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); if(root != null) stack.push(root); while(!stack.isEmpty()){ TreeNode tmp = stack.pop(); if(tmp != null){ if(tmp.right != null) stack.push(tmp.right); //右节点入栈 stack.push(tmp); //根节点入栈 stack.push(null); //入栈空节点 if(tmp.left != null) stack.push(tmp.left); //左节点入栈 }else{ res.add(stack.pop().val); } } return res; } }
-
后序遍历:
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); if(root != null) stack.push(root); while(!stack.isEmpty()){ TreeNode tmp = stack.pop(); if(tmp != null){ stack.push(tmp); //根节点入栈 stack.push(null); //入栈空节点 if(tmp.right != null) stack.push(tmp.right); //右节点入栈 if(tmp.left != null) stack.push(tmp.left); //左节点入栈 }else{ res.add(stack.pop().val); } } return res; } }