前序/中序/后序/层序
一、二叉树的遍历
遍历是数据结构中的常见操作,就是把所有元素都访问一遍。
线性数据结构的遍历比较简单:正序遍历、逆序遍历。
根据节点访问顺序的不同,二叉树的常见遍历方式有4种:
- 前序遍历(Preorder Traversal)
- 中序遍历(Inorder Traversal)
- 后序遍历(Postorder Traversal)
- 层序遍历(Level Order Traversal)
1.1. 前序遍历
访问顺序:根节点 -> 前序遍历左子树 -> 前序遍历右子树。
如上图,前序遍历的结果就是:7、4、2、1、3、5、9、8、11、10、12
。
1.1.1. 递归
1 | public void preorderTraversal() { |
1.1.2. 栈
实现思路:
- 设置
node = root
- 循环执行以下操作(如果
node != null
,对node
进行访问):- 将
node.right
入栈 - 设置
node = node.left
- 如果
node == null
- 如果栈为空,结束遍历
- 如果栈不为空,弹出栈顶元素并赋值给
node
,继续进入循环
- 将
1 | public void preorderTraversalWithStack() { |
1.2. 中序遍历
访问顺序:中序遍历左子树 -> 根节点 -> 中序遍历右子树。
如上图,中序遍历的结果就是:1、2、3、4、5、7、8、9、10、11、12
。
如果访问顺序是中序遍历右子树、根节点、中序遍历左子树
呢?遍历结果:12、11、10、9、8 、7、5、4、3、2、1
。
二叉搜索树的中序遍历结果是升序或者降序的。
1.2.1. 递归
1 | public void inorderTraversal() { |
1.2.2. 栈
实现思路:
- 设置
node = root
- 循环执行以下操作:
- 如果
node != null
- 将
node
入栈 - 设置
node = node.left
- 将
- 如果
node == null
- 如果栈为空,结束遍历
- 如果栈不为空,弹出栈顶元素并赋值给
node
- 对
node
进行访问 - 设置
node = node.right
- 如果
1 | public void inorderTraversalWithStack() { |
1.3. 后序遍历
访问顺序:后序遍历左子树 -> 后序遍历右子树 -> 根节点。
如上图,后序遍历的结果就是:1、3、2、5、4、8、10、12、11、9、7
。
1.3.1. 递归
1 | public void postorderTraversal() { |
1.3.2. 栈
实现思路:
- 将
root
入栈 - 循环执行以下操作,直到栈为空
- 如果栈顶节点是叶子节点 或者 上一次访问的节点是栈顶节点的子节点
- 弹出栈顶节点,进行访问
- 否则
- 将栈顶节点的right、left按顺序入栈
- 如果栈顶节点是叶子节点 或者 上一次访问的节点是栈顶节点的子节点
1 | public void postorderTraversalWithStack() { |
1.4. 层序遍历
访问顺序:从上到下、从左到右依次访问每一个节点。
如上图,层序遍历的结果就是:7、4、9、2、5、8、11、1、3、10、12
。
实现思路:使用队列。
- 将根节点入队;
- 循环执行以下操作,直到队列为空:
- 将队头节点
A
出队,进行访问 - 将
A
的左子节点入队 - 将
A
的右子节点入队
- 将队头节点
1 | public void levelOrderTraversal() { |
1.5. 增强遍历(设计接口)
如果允许外界遍历二叉树的元素?应该如何设计接口?
- 让使用者可以自定义操作访问的元素
- 指定节点终止遍历
如果是Java,可以增加一个接口(interface
)或抽象类(abstract
);如果是iOS,可以使用闭包或者代理。
以Java为例:
1 | // 定义抽象类(在Java中interface不能定义变量,abstract才可以定义变量) |
二、遍历的应用
前序遍历:树状结构展示(注意左右子树的顺序)。
中序遍历:二叉搜索树的中序遍历按升序或者降序处理节点。
后序遍历:适用于一些先子后父的操作。
层序遍历:计算二叉树的高度;判断一棵树是否为完全二叉树。
2.1. 计算二叉树高度
2.1.1. 递归
1 | public int height() { |
2.1.2. 迭代
1 | public int height() { |
2.2. 判断一棵树是否为完全二叉树
实现思路:
- 如果树为空,返回
false
- 如果树不为空,开始层序遍历二叉树(用队列)
- 如果
node.left!=null
,将node.left
入队 - 如果
node.left==null && node.right!=null
,返回false
(完全二叉树度为1的节点必须左对齐) - 如果
node.right!=null
,将node.right
入队 - 如果
node.right==null
- 那么后面遍历的节点应该都为叶子节点,才是完全二叉树,返回
true
- 否则返回
false
- 那么后面遍历的节点应该都为叶子节点,才是完全二叉树,返回
- 如果
1 | // 判断是否为完全二叉树 |
2.3. 翻转二叉树
递归(使用前/后/中序遍历方式都可以):
1 | public TreeNode invertTree(TreeNode root) { |
迭代(使用层序遍历):
1 | public TreeNode invertTree(TreeNode root) { |
三、LeetCode
二叉树的前序遍历:https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
二叉树的中序遍历:https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
二叉树的后序遍历:https://leetcode-cn.com/problems/binary-tree-postorder-traversal/
二叉树的层次遍历:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
二叉树的层次遍历II:https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/
二叉树的最大深度:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
二叉树最大宽度:https://leetcode-cn.com/problems/maximum-width-of-binary-tree/
N叉树的前序遍历:https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/
N叉树的后序遍历:https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal/
N叉树的最大深度:https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/
二叉树展开为链表:https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/
从中序与后序遍历序列构造二叉树:https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
从前序与中序遍历序列构造二叉树:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
根据前序和后序遍历构造二叉树:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/