这里就对二叉树的遍历方式进行总结
基于递归的遍历
前序遍历
前序遍历:即对二叉树按照当前节点、左子树、右子树的顺序进行遍历。显然借助递归,非常容易实现、理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> list = new LinkedList<>(); dfs(root, list); return list; }
private void dfs(TreeNode cur, List<Integer> list) { if( cur==null ) { return; }
list.add( cur.val );
dfs(cur.left, list);
dfs(cur.right, list); } }
|
中序遍历
中序遍历:即对二叉树按照左子树、当前节点、右子树的顺序进行遍历。显然借助递归,非常容易实现、理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> list = new LinkedList<>(); dfs(root, list); return list; }
private void dfs(TreeNode cur, List<Integer> list) { if( cur==null ) { return; }
dfs(cur.left, list);
list.add( cur.val );
dfs(cur.right, list); } }
|
后序遍历
后序遍历:即对二叉树按照左子树、右子树、当前节点的顺序进行遍历。显然借助递归,非常容易实现、理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> list = new LinkedList<>(); dfs(root, list); return list; }
private void dfs(TreeNode cur, List<Integer> list) { if( cur==null ) { return; }
dfs(cur.left, list);
dfs(cur.right, list);
list.add( cur.val ); } }
|
基于迭代的遍历
前序遍历
不同于递归隐式维护栈的便捷,在通过迭代进行遍历时需要我们显式地维护一个栈。具体地,在前序遍历当中。首先需要处理对当前节点进行处理、然后沿着左子树一直入栈。当左子树遍历完毕, 通过出栈获取父节点来对右子树再进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new LinkedList<>(); LinkedList<TreeNode> stack = new LinkedList<>();
while ( root!=null || !stack.isEmpty() ) { while (root!=null) { res.add( root.val ); stack.addLast( root ); root = root.left; }
TreeNode parent = stack.removeLast(); root = parent.right; }
return res; } }
|
中序遍历
不同于递归隐式维护栈的便捷,在通过迭代进行遍历时需要我们显式地维护一个栈。具体地,在中序遍历当中。首先沿着左子树一直入栈。当左子树遍历完毕, 通过出栈获取父节点并对其进行处理,最后对右子树再进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new LinkedList<>(); LinkedList<TreeNode> stack = new LinkedList<>();
while ( root!=null || !stack.isEmpty() ) { while ( root!=null ) { stack.addLast( root ); root = root.left; }
TreeNode parent = stack.removeLast(); res.add( parent.val ); root = parent.right; }
return res; } }
|
后序遍历
后序遍历的顺序是左、右、当前。如果直接使用栈按这个顺序进行遍历输出会比较麻烦。故我们可以先按照当前、右、左的顺序进行迭代遍历,显然这个过程与前序遍历非常类似,只是需要把代码中涉及左、右子树的调换下即可。最后对遍历结果进行翻转。这样遍历结果就由当前、右、左 就变为 左、右、当前。即我们所需的后序遍历结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new LinkedList<>(); LinkedList<TreeNode> stack = new LinkedList<>();
while ( root!=null || !stack.isEmpty() ) { while (root!=null) { res.add( root.val ); stack.addLast( root ); root = root.right; }
TreeNode parent = stack.removeLast(); root = parent.left; }
Collections.reverse( res ); return res; } }
|
层序遍历
对于二叉树的层序遍历就简单很多了。我们借助队列的FIFO特性即可轻松实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Solution { public List<Integer> levelOrder(TreeNode root) { List<Integer> res = new LinkedList<>(); if (root == null) { return res; }
Queue<TreeNode> queue = new LinkedList<>(); queue.add(root);
while ( !queue.isEmpty() ) { TreeNode cur = queue.poll(); res.add( node.val );
if( node.left != null ) { queue.add( node.left ); } if( node.right != null ) { queue.add( node.right ); } }
return res; } }
|
基于Morris算法的遍历
基本原理
Morris算法为二叉树的遍历提供了新的思路,其通过充分利用树中叶子节点存在大量空指针这一特点。实现了常数级别的空间复杂度。在进一步介绍该算法之前,我们先来说明一个概念——mostRight节点。其用于表示当前节点cur的左子树的最右节点。例如下图的一个二叉树。如果当前节点为1,则mostRight节点即为5;如果当前节点为3,则mostRight节点即为6
实际上,Morris算法非常简单。其基本遍历流程如下:
- 将当前节点cur初始化为root
- 当前节点cur如果不存在左子树。则将当前节点指向其右子树,以便遍历当前节点的右子树
- 当前节点cur如果存在左子树,则先获取cur节点的mostRight节点
- 如果mostRight节点的右子节点为空,此时说明cur节点的左子树还未遍历。故:一方面,我们将mostRight节点的右子节点设置为cur节点;另一方面,将当前节点指向其左子树,以便遍历当前节点的左子树
- 如果mostRight节点的右子节点为当前节点cur,此时说明cur节点的左子树已经遍历完成。故:一方面,我们将mostRight节点的右子节点设置为null空指针;另一方面,将当前节点指向其右子树,以便遍历当前节点的右子树
- 重复Step 2、Step 3,直到当前节点cur为null时为止
该算法遍历过程的示意图如下,可以看到当一个节点的左子树未遍历完时,Morris算法会利用原叶子节点的null空指针修改树。而当该节点的左子树遍历后,会把对树的修改进行撤回。以恢复树原有的结构
前序遍历
这里基于Morris算法来实现前序遍历。问题的关键就在于何时对当前节点进行处理。显然这里有两个时机,一方面,遍历过程中发现当前节点的左子树为空。则在对当前节点的右子树进行遍历前,需要对当前节点进行处理;另一方面,遍历过程中发现当前节点的左子树不为空,则在对当前节点的左子树进行遍历前,需要对当前节点进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new LinkedList<>(); if( root == null ) { return res; }
TreeNode cur = root; TreeNode mostRight = null;
while ( cur != null ) { if( cur.left == null ) { res.add( cur.val ); cur = cur.right; } else { mostRight = cur.left; while ( mostRight.right!=null && mostRight.right!=cur ) { mostRight = mostRight.right; }
if( mostRight.right == null) { res.add( cur.val ); mostRight.right = cur; cur = cur.left; } else if ( mostRight.right == cur ) { mostRight.right = null; cur = cur.right; } } }
return res; } }
|
中序遍历
这里基于Morris算法来实现中序遍历。问题的关键就在于何时对当前节点进行处理。显然这里有两个时机,一方面,遍历过程中发现当前节点的左子树为空。则在对当前节点的右子树进行遍历前,需要对当前节点进行处理;另一方面,遍历过程中发现当前节点的左子树不为空,则在对当前节点的左子树完成遍历后,需要对当前节点进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new LinkedList<>(); if( root == null ) { return res; }
TreeNode cur = root; TreeNode mostRight = null;
while ( cur != null ) { if( cur.left == null ) { res.add( cur.val ); cur = cur.right; } else { mostRight = cur.left; while ( mostRight.right!=null && mostRight.right!=cur ) { mostRight = mostRight.right; }
if( mostRight.right == null) { mostRight.right = cur; cur = cur.left; } else if ( mostRight.right == cur ) { res.add( cur.val ); mostRight.right = null; cur = cur.right; } } }
return res; } }
|
Note
这里给出本文中关于树节点的类定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
class TreeNode { TreeNode left; TreeNode right; int val; TreeNode() { } TreeNode(int val) { this.val = val; } TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; } }
|