二叉树理论基础篇
一、二叉树的种类
在我们解题过程中二叉树有两种主要的形式:满二叉树和完全二叉树。
1、满二叉树
满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
如图所示:
这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。
2、完全二叉树
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
3、二叉搜索树
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
4、平衡二叉搜索树
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
二、二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。
链式存储是大家很熟悉的一种方式,那么我们来看看如何顺序存储呢?
其实就是用数组来存储二叉树,顺序存储的方式如图:
三、二叉树的遍历方式
二叉树主要有两种遍历方式:
- 深度优先遍历:先往深走,遇到叶子节点再往回走。
- 广度优先遍历:一层一层的去遍历。
这两种遍历是图论中最基本的两种遍历方式,后面在介绍图论的时候 还会介绍到。
那么从深度优先遍历和广度优先遍历进一步拓展,才有如下遍历方式:
- 深度优先遍历
- 前序遍历(递归法,迭代法)
- 中序遍历(递归法,迭代法)
- 后序遍历(递归法,迭代法)
- 广度优先遍历
- 层次遍历(迭代法)
在深度优先遍历中:有三个顺序,前中后序遍历, 有同学总分不清这三个顺序,经常搞混,我这里教大家一个技巧。
这里前中后,其实指的就是中间节点的遍历顺序,只要大家记住 前中后序指的就是中间节点的位置就可以了。
看如下中间节点的顺序,就可以发现,中间节点的顺序就是所谓的遍历方式
- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中
大家可以对着如下图,看看自己理解的前后中序有没有问题。
四、二叉树的定义
package com.yzu.lee.treenode;
import org.junit.Test;
/**
* @ClassName: TreeNode
* @Description:树定义
* @author: Leekuangyew
* @date: 2022/5/22 9:34
*/
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
二叉树的递归遍历
144.二叉树的前序遍历
package com.yzu.lee.treenode;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: PreorderTraversal
* @Description:给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
* @author: Leekuangyew
* @date: 2022/5/22 9:37
*/
public class PreorderTraversal {
public List<Integer> preorderTraversal(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
preorder(root, result);
return result;
}
private void preorder(TreeNode root, ArrayList<Integer> result) {
if (root == null) {
return;
}
result.add(root.val);
preorder(root.left, result);
preorder(root.right, result);
}
}
94.二叉树的中序遍历
package com.yzu.lee.treenode;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: InorderTraversal
* @Description:给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
* @author: Leekuangyew
* @date: 2022/5/22 9:46
*/
public class InorderTraversal {
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
inorder(root, result);
return result;
}
private void inorder(TreeNode root, ArrayList<Integer> result) {
if (root == null) {
return;
}
inorder(root.left, result);
result.add(root.val);
inorder(root.right, result);
}
}
145.二叉树的后序遍历
package com.yzu.lee.treenode;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: PostorderTraversal
* @Description:给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。
* @author: Leekuangyew
* @date: 2022/5/22 9:50
*/
public class PostorderTraversal {
public List<Integer> postorderTraversal(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
postorder(root, result);
return result;
}
private void postorder(TreeNode root, ArrayList<Integer> result) {
if (root == null) {
return;
}、
postorder(root.left, result);
postorder(root.right, result);
result.add(root.val);
}
}
二叉树的迭代遍历
144.二叉树的前序遍历
层次遍历使用迭代法才用队列,深度优先遍历时迭代法使用栈实现
思路:
- 创建栈,并将根节点压入栈
- while循环,当栈不为空,弹出栈顶节点,添加到结果数组,后判断右节点与左节点是否为空,不为空则加入
//迭代法
public List<Integer> preorderTraversal2(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node
= stack.pop();
result.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return result;
}
94.二叉树的中序遍历
思路:
在迭代的过程中,其实有两个操作:
- 处理:将元素放进result数组中
- 访问:遍历节点
为什么刚刚写的前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。
那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
- 中序迭代遍历不需要先将root加入栈中
//迭代法
public List<Integer> inorderTraversal2(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode node = root;
while (node != null || !stack.isEmpty()) {
if (node != null) {
stack.push(node);
node = node.left;
} else {
node = stack.pop();
result.add(node.val);
node = node.right;
}
}
return result;
}
145.二叉树的后序遍历
- 与前序相似,只需要将中右左的添加顺序改成中左右,最后反转结果数组
//迭代法
public List<Integer> postorderTraversal2(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null) {
stack.push(node.left);
}
if (node.right != null) {
stack.push(node.right);
}
}
Collections.reverse(result);
return result;
}
二叉树的统一迭代法
144.二叉树的前序遍历
思路:
- **将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。**标记,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
//统一迭代法
public List<Integer> preorderTraversal3(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode node = stack.peek(); //判断栈顶节点是否为null
if (node != null) {
stack.pop(); //如果不为null,则将栈顶弹出 并按中左右节点添加到栈里
if (node.right != null) stack.push(node.right);//添加右节点
if (node.left != null) stack.push(node.left);//添加左节点
stack.push(node);//添加中节点
stack.push(null);//添加完中节点后,需要对其进行处理 增加null标记
} else {// 只有遇到空节点的时候,才将下一个节点放进结果集
stack.pop();//弹出空节点
node = stack.peek();//取出栈顶节点
stack.pop();
result.add(node.val);//加入结果集
}
}
return result;
}
94.二叉树的中序遍历
//统一迭代法
public List<Integer> inorderTraversal3(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode node = stack.peek(); //判断栈顶节点是否为null
if (node != null) {
stack.pop(); //如果不为null,则将栈顶弹出 并按左中右节点添加到栈里
if (node.right != null) stack.push(node.right);//添加右节点
stack.push(node);//添加中节点
stack.push(null);//添加完中节点后,需要对其进行处理 增加null标记
if (node.left != null) stack.push(node.left);//添加左节点
} else {// 只有遇到空节点的时候,才将下一个节点放进结果集
stack.pop();//弹出空节点
node = stack.peek();//取出栈顶节点
stack.pop();
result.add(node.val);//加入结果集
}
}
return result;
}
145.二叉树的后序遍历
//统一迭代法
public List<Integer> postorderTraversal3(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode node = stack.peek(); //判断栈顶节点是否为null
if (node != null) {
stack.pop(); //如果不为null,则将栈顶弹出 并按中左右节点添加到栈里
stack.push(node);//添加中节点
stack.push(null);//添加完中节点后,需要对其进行处理 增加null标记
if (node.right != null) stack.push(node.right);//添加右节点
if (node.left != null) stack.push(node.left);//添加左节点
} else {// 只有遇到空节点的时候,才将下一个节点放进结果集
stack.pop();//弹出空节点
node = stack.peek();//取出栈顶节点
stack.pop();
result.add(node.val);//加入结果集
}
}
return result;
}
102.二叉树的层序遍历
题意:给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
思路:
-
递归法:
- 确定递归函数的参数和返回值:
checkFun02(root, 0);
- 确定终止条件:
if (node == null) return;
- 确定单层递归的逻辑:
- 将传入的deep加一,表示所在的层数上升
- 当结果集合的大小小于deep时,创建一个集合,放入结果结合中
- 将当前节点的值填入到集合中对应的位置
- 继续递归当前节点的左节点和右节点
- 确定递归函数的参数和返回值:
-
迭代法:
- 判断根节点是否为空,不为空,则创建一个队列,将根节点放入到队列当中
- 循环判断队列是否为空,如果不为空,则获取队列的大小,如果队列的size大于0,则弹出当前队首节点,并将该节点存到结果集合中,并将非空的左右子节点放入队列中
- 每次循环将结果保存到集合中
package com.yzu.lee.treenode;
import com.sun.istack.internal.NotNull;
import com.sun.javafx.image.IntPixelGetter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* @ClassName: LevelOrder
* @Description:
* @author: Leekuangyew
* @date: 2022/5/25 13:53
*/
public class LevelOrder {
private List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
//迭代法 利用队列
checkFun01(root);
//递归法
checkFun02(root, 0);
return result;
}
private void checkFun02(TreeNode node, int deep) {
if (node == null) return;
deep++;
if (result.size() < deep) {
ArrayList<Integer> list = new ArrayList<>();
result.add(list);
}
result.get(deep - 1).add(node.val);
checkFun02(node.left, deep);
checkFun02(node.right, deep);
}
private void checkFun01(TreeNode root) {
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
ArrayList<Integer> list = new ArrayList<>();
while (size > 0) {
TreeNode node = queue.poll();
list.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
size--;
}
result.add(list);
}
}
}
107.二叉树的层次遍历II
题意:给你二叉树的根节点 root
,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
思路:
- 递归法,层序遍历,然后反转
- 迭代法,层序遍历,然后反转
package com.yzu.lee.treenode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @ClassName: LevelOrderBottom
* @Description:给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
* @author: Leekuangyew
* @date: 2022/5/25 15:09
*/
public class LevelOrderBottom {
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> levelOrderBottom(TreeNode root) {
levelOrder(root, 0);
Collections.reverse(result);
return result;
}
private void levelOrder(TreeNode node, int deep) {
if (node == null) return;
int size = result.size();
deep++;
if (size < deep) {
List<Integer> list = new ArrayList<>();
result.add(list);
}
result.get(deep - 1).add(node.val);
if (node.left != null) levelOrder(node.left, deep);
if (node.right != null) levelOrder(node.right, deep);
}
}
199. 二叉树的右视图
题意:给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
思路:
- 利用BFS进行层次遍历,记录下每层的最后一个元素。
- DFS
package com.yzu.lee.treenode;
import com.sun.org.apache.bcel.internal.generic.NEW;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.Inflater;
/**
* @ClassName: RightSideView
* @Description:给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
* @author: Leekuangyew
* @date: 2022/5/25 17:21
*/
public class RightSideView {
List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<Integer> rightSideView(TreeNode root) {
rightSide(root, 0);
ArrayList<Integer> result = new ArrayList<>();
for (List<Integer> list : resList) {
result.add(list.get(list.size() - 1));
}
return result;
}
private void rightSide(TreeNode node, int deep) {
if (node == null) return;
deep++;
if (resList.size() < deep) {
List<Integer> list = new ArrayList<>();
resList.add(list);
}
resList.get(deep - 1).add(node.val);
if (node.left != null) rightSide(node.left, deep);
if (node.right != null) rightSide(node.right, deep);
}
}
637.二叉树的层平均值
题意:给定一个非空二叉树的根节点 root
, 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5
以内的答案可以被接受。
思路:
- 利用BFS进行层次遍历,然后对每一层的结果进行取平均值操作
package com.yzu.lee.treenode;
import com.sun.org.apache.bcel.internal.generic.NEW;
import org.w3c.dom.ls.LSException;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: AverageOfLevels
* @Description:
* @author: Leekuangyew
* @date: 2022/5/25 17:32
*/
public class AverageOfLevels {
List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<Double> averageOfLevels(TreeNode root) {
averageOfLevels(root, 0);
ArrayList<Double> result = new ArrayList<>();
for (List<Integer> list : resList) {
int size = list.size();
double sum = 0;
for (Integer integer : list) {
sum += integer;
}
Double res = sum / (double) size;
result.add(res);
}
return result;
}
private void averageOfLevels(TreeNode node, int deep) {
if (node == null) return;
deep++;
int size = resList.size();
if (size < deep) {
ArrayList<Integer> list = new ArrayList<>();
resList.add(list);
}
resList.get(deep - 1).add(node.val);
if (node.left != null) averageOfLevels(node.left, deep);
if (node.right != null) averageOfLevels(node.right, deep);
}
}
429.N叉树的层序遍历
题意:给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
思路:
- 利用BFS的递归法层次遍历,只有对于操作子节点的方式不同
package com.yzu.lee.treenode;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: LevelOrderT
* @Description:给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
* 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
* @author: Leekuangyew
* @date: 2022/5/25 18:21
*/
public class LevelOrderT {
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> levelOrder(Node root) {
levelOrder(root, 0);
return result;
}
private void levelOrder(Node node, int deep) {
if (node == null) return;
deep++;
int size = result.size();
if (size < deep) {
ArrayList<Integer> list = new ArrayList<>();
result.add(list);
}
result.get(deep - 1).add(node.val);
if (node.children != null) {
for (Node child : node.children) {
levelOrder(child, deep);
}
}
}
}
515.在每个树行中找最大值
题意:给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
思路:
- 利用BFS进行递归层次遍历,定义一个Map来存储每一层的最大值
package com.yzu.lee.treenode;
import com.sun.org.apache.bcel.internal.generic.NEW;
import org.junit.Test;
import java.util.*;
/**
* @ClassName: LargestValues
* @Description:给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
* @author: Leekuangyew
* @date: 2022/5/25 18:37
*/
public class LargestValues {
Map<Integer, Integer> resList = new HashMap<>();
public List<Integer> largestValues(TreeNode root) {
largestValues(root, 0);
ArrayList<Integer> list = new ArrayList<>();
int i = 0;
while (resList.containsKey(i)) {
list.add(resList.get(i));
i++;
}
return list;
}
private void largestValues(TreeNode node, int deep) {
if (node == null) return;
deep++;
if (resList.containsKey(deep - 1)) {
resList.put(deep - 1, Math.max(resList.get(deep - 1), node.val));
} else {
resList.put(deep - 1, node.val);
}
if (node.left != null) largestValues(node.left, deep);
if (node.right != null) largestValues(node.right, deep);
}
}
116.填充每个节点的下一个右侧节点指针
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
思路:
- 利用BFS进行迭代层次遍历,在迭代过程中,将每一层中的节点的next等于队列的头节点
node.next = queue.peek();
package com.yzu.lee.treenode.node;
/**
* @ClassName: Node
* @Description:
* @author: Leekuangyew
* @date: 2022/5/25 20:12
*/
public class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {
}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
}
package com.yzu.lee.treenode.node;
import java.util.LinkedList;
import java.util.Queue;
/**
* @ClassName: Connect
* @Description:
* @author: Leekuangyew
* @date: 2022/5/25 20:12
*/
public class Connect {
public Node connect(Node root) {
Queue<Node> queue = new LinkedList<>();
if (root == null) return root;
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size > 0) {
Node node = queue.poll();
size--;
if (size > 0) {
node.next = queue.peek();
}
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return root;
}
}
117.填充每个节点的下一个右侧节点指针II
题意:给定一个二叉树
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
思路:
- 与116题类似
package com.yzu.lee.treenode.node;
import java.util.LinkedList;
import java.util.Queue;
/**
* @ClassName: Connect
* @Description:
* @author: Leekuangyew
* @date: 2022/5/25 20:12
*/
public class Connect {
public Node connect(Node root) {
Queue<Node> queue = new LinkedList<>();
if (root == null) return root;
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size > 0) {
Node node = queue.poll();
size--;
if (size > 0) {
node.next = queue.peek();
}
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return root;
}
}
226.翻转二叉树
题意:翻转一棵二叉树
思路:
- 利用DFS进行递归 前序
- 确定递归函数的参数和返回值:
invertTree(TreeNode root)
- 确定终止条件:
if (root == null) return root;
- 确定单层递归的逻辑:
swap(root);
并返回root。
- 确定递归函数的参数和返回值:
- 利用BFS进行迭代
- 从第一层开始,依次往下交换左右节点
package com.yzu.lee.treenode;
import java.util.LinkedList;
import java.util.Queue;
/**
* @ClassName: InvertTree
* @Description:给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
* @author: Leekuangyew
* @date: 2022/5/25 22:35
*/
public class InvertTree {
// public TreeNode invertTree(TreeNode root) {
// //深度优先DFS
// if (root == null) return root;
// swap(root);
// invertTree(root.left);
// invertTree(root.right);
// return root;
// }
//
// private void swap(TreeNode root) {
// TreeNode temp = root.left;
// root.left = root.right;
// root.right = temp;
// }
public TreeNode invertTree(TreeNode root) {
//广度优先 BFS
if (root == null) return null;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
TreeNode node = queue.poll();
swap(node);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return root;
}
private void swap(TreeNode node) {
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
}