二叉树的各种遍历
递归遍历
中序遍历:中序遍历的顺序是左根右,递归写法比较简单。直接贴上模板
public void inorder(TreeNode root){
if(root == null) return;
if(root.left != null) inorder(root.left);
//当前位置即为操作,可以根据题意进行添加
if(root.right != null) inorder(root.right);
}
例如,lettcode94题遍历二叉树使用中序遍历的方法,但是需要放回一个list,那么可以在上面代码注释处添加这部分的逻辑
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res){
if(root == null) return;
if(root.left != null) inorder(root.left, res);
res.add(root.val);
if(root.right != null) inorder(root.right, res);
}
那么,直接举一返二,写出先序遍历和后序遍历的写法,只需要将操作的逻辑代码放置在不同位置即可
//先序遍历
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void preorder(TreeNode root, List<Integer> res){
if(root == null) return;
res.add(root.val);
if(root.left != null) inorder(root.left, res);
if(root.right != null) inorder(root.right, res);
}
//后序遍历
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void postorder(TreeNode root, List<Integer> res){
if(root == null) return;
if(root.left != null) inorder(root.left, res);
if(root.right != null) inorder(root.right, res);
res.add(root.val);
}
非递归遍历
递归遍历的优点是代码简洁,但是在二叉树的节点比较多的时候,容易出现爆栈StackOverFlow,所以我们一般也会写上二叉树的非递归写法,一般来说递归写法都就可以更改为非递归写法,这就需要借助一种数据结构栈,栈的特点是先进后出,对于中序遍历来说是先遍历左节点的,那么可以先将所有左节点加入至栈中,然后将最后一个左节点拿出来得到值,然后取出右节点,重复即可
public List<Integer> inorder(TreeNode root){
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
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;
}
有了中序遍历的基础,写先序遍历就可以直接写出来了
public List<Integer> perorder(TreeNode root){
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
while(root != null || !stack.isEmpty()){
while(root != null){
res.add(root.val);
stack.push(root);
root = root.left;
}
root = stack.pop();
root = root.right;
}
return res;
}
那么后序遍历呢?进行后序遍历的时候,需要判断当前节点是否具有左右节点,比较麻烦,这里使用一种比较灵活的方法,因为先序遍历是根左右,而后序遍历是左右根,可以将原二叉树的左右子树进行颠倒,然后将先序遍历的结果反过来既可以得到后序遍历的结果,例如
原树:
1
/ \
2 3
/ \ / \
7 6 4 5
后序遍历: 7 6 2 4 5 3 1
反转左右节点
1
/ \
3 2
/ \ / \
5 4 6 7
先序遍历: 1 3 5 4 2 6 7
将先序遍历反转得到:7 6 2 4 5 3 1就是原树的后序遍历,而反转左右子树比较简单可以做到,只需要在先序遍历的时候先遍历右子树,然后遍历左子树即可。
public List<Integer> postorder(TreeNode root){
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
while(root != null || !stack.isEmpty()){
while(root != null){
res.add(root.val);
stack.push(root);
root = root.right;
}
root = stack.pop();
root = root.left;
}
Collections.reverse(res);
return res;
}
层次遍历
层次遍历是指按照从上到下,从左到右的顺序,那么一般可以使用BFD,广度优先算法,而这个算法的实现需要借助一个数据结构队列,队列的优点是先进先出
为什么使用队列?
3
/ \
9 20
/ \
15 7
返回:
[3,9,20,15,7]
根据上面的例子,可知我们需要将每一个节点保存下来,以便获取到该节点可能的左右子树节点,如果而打印是从左到右打印的,使用队列先进先出恰好满足这个特点
public int[] levelOrder(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> q = new LinkedList<>();
if(root == null) return new int[]{
};
q.add(root);
while(!q.isEmpty()){
TreeNode temp = q.poll();
res.add(temp.val);
if(temp.left != null) q.add(temp.left);
if(temp.right != null) q.add(temp.right);
}
int[] r = new int[res.size()];
for(int i = 0;i < res.size();i++){
r[i] = res.get(i);
}
return r;
}
这里也简单的复习一下Deque作为栈和队列的一些常用方法
作为栈
方法 | 作用 |
---|---|
push(e) | 弹入 |
pop() | 弹出 |
peek() | 取栈顶元素 |
作为队列
方法 | 作用 |
---|---|
offer(e) | 向队首加入 |
poll() | 从队尾排除 |
peek() | 从队尾取 |
练习
剑指offer32题 1
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
与层次遍历不同的是,这个题目还需要将每一层的数据打印成一行,这就需要将因为队列里面存储的是每一行的数据,这就需要每循环的时候对当前的队列进行操作,可以先将队列的size记住,然后把子节点加入
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
Deque<TreeNode> q = new LinkedList<>();
if(root == null) return new ArrayList<List<Integer>>();
q.offer(root);
while(!q.isEmpty()){
List<Integer> tempA = new ArrayList<>();
//这行代码很重要,需要记住当前层的队列数量
// for(int i = queue.size(); i > 0; i--)
int size = q.size();
for(int i = 0;i < size;i++){
TreeNode temp = q.poll();
if(temp.left != null) q.offer(temp.left);
if(temp.right != null) q.offer(temp.right);
tempA.add(temp.val);
}
res.add(tempA);
}
return res;
}
剑指offer32题 2
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]
这是上面题目的变种,只需要遇到偶数行将数据进行反转即可
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
Deque<TreeNode> q = new LinkedList<>();
if(root == null) return new ArrayList<>();
int flag = 1;
q.offer(root);
while(!q.isEmpty()){
List<Integer> temp = new ArrayList<>();
for(int i = q.size();i >0 ;i--){
TreeNode node = q.poll();
temp.add(node.val);
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
if(flag % 2 == 0){
Collections.reverse(temp);
}
res.add(temp);
flag++;
}
return res;
}