二叉树的基本构造
public class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
TreeNode() {}
public TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
二叉树术语
度,“度”指的是一个节点拥有的子节点的数量。在一颗二叉树中,一个节点的度可以是0、1或2:
- 度为0:表示该节点是一个叶子节点,即它没有子节点。
- 度为1:表示该节点有一个子节点,可以是一个左子节点或一个右子节点。
- 度为2:表示该节点有两个子节点,既有左子节点又有右子节点。
二叉树的类型
当然,这四种二叉树在数据结构中有着不同的定义和特性:
1. 满二叉树(Full Binary Tree)
满二叉树是一种特殊的二叉树,其中每个节点要么没有子节点(即度为0,是叶子节点),要么有两个子节点(即度为2)。这意味着在满二叉树中不存在只有一个子节点的节点。另外,满二叉树的所有叶子节点都位于同一层级。
2. 完全二叉树(Complete Binary Tree)
完全二叉树是介于满二叉树和平衡二叉树之间的一种形态。在完全二叉树中,所有的层级都被完全填满,除了最后一层。在最后一层上,所有的节点都尽可能地向左对齐。这意味着如果最后一层不完整,那么未填满的部分一定在该层的右侧。
3. 二叉搜索树(Binary Search Tree,BST)
二叉搜索树是一种特殊的二叉树,对于树中的每个节点,其左子树上的所有节点的值都小于它的节点值,其右子树上的所有节点的值都大于它的节点值。二叉搜索树支持高效的节点查找、插入和删除操作。
4. 平衡二叉搜索树(Balanced Binary Search Tree)
平衡二叉搜索树是一种特殊的二叉搜索树,其中任何节点的两个子树的高度差不超过1。这种高度平衡保证了树的操作(如查找、插入、删除)在最坏情况下都能保持对数时间复杂度。AVL树和红黑树是平衡二叉搜索树的两个例子,它们通过旋转等操作来维护树的平衡。
二叉树的遍历方式
深度优先遍历(Depth-First Search, DFS)
-
前序遍历(Pre-order Traversal) 在前序遍历中,遍历的顺序是“根-左-右”。首先访问根节点,然后遍历左子树,最后遍历右子树。
-
中序遍历(In-order Traversal) 中序遍历的顺序是“左-根-右”。首先遍历左子树,然后访问根节点,最后遍历右子树。对于二叉搜索树,中序遍历的结果是按键值升序访问树中所有节点的遍历方式。
-
后序遍历(Post-order Traversal) 后序遍历的顺序是“左-右-根”。首先遍历左子树,然后遍历右子树,最后访问根节点。
广度优先遍历(Breadth-First Search, BFS)
- 层序遍历(Level-order Traversal) 层序遍历从树的根开始,按照层级从上到下,每层从左到右的顺序逐个访问所有节点。这通常是通过使用队列来实现的。
二叉树中的深度和高度
深度(Depth)
根节点到任意结点的距离(按层序遍历划分,如根节点为1,则它的左右子树为2),每当向下访问树的下一层,则距离加1
高度(Height)
任意结点到叶子结点的距离(按层序遍历划分,如叶子结点为1,则它的根节点为2),每当向上访问树的上一层,则距离加1
什么是最大深度
即根结点到叶子结点的最远距离(也就是根节点到最底层的叶子节点的距离)。注意:对一颗二叉来说,求它的最大深度也就相当于求整棵树的高度(即根节点的高度)。解法:即可以使用前序遍历,也可以使用后序遍历。
什么是最小深度
即根节点到它第一个为null的子节点的距离(这里的子节点包含根节点的左右结点,左右结点的孩子)
二叉树的基本遍历实现
前序遍历
public static void main(String[] args) {
TreeNode node = new TreeNode(1);
node.left = new TreeNode(2);
node.left.left = new TreeNode(3);
node.left.right = new TreeNode(4);
node.right = new TreeNode(2);
node.right.right = new TreeNode(3);
node.right.left = new TreeNode(4);
levelOrderTraversal(node).forEach(System.out::print);
System.out.println();
}
/**
* 什么是DP Dynamic Programming
* 一种优化问题求解方法,通常用于解决具有 重叠子问题 和 最优子结构 性质的问题
* 基本思想是将原问题分解成更小的子问题,通过求解和保存这些子问题的解,避免重复计算,从而提高算法的效率
*/
/**
* 实现二叉树的前序遍历。
*
* @param root 二叉树的根节点
* @return
*/
public static List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
//递归
preorderDP(root, res);
//迭代
preorder(root);
return res;
}
public static void preorderDP(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
res.add(root.val);
preorderDP(root.left, res);
preorderDP(root.right, res);
}
/**
* 前序遍历 中 左 右
* 入栈顺序 中 右 左
* 出栈顺序 中 左 右
*
* @param root
* @return
*/
public static List<Integer> preorder(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
//根据栈的特性,先进后出;所以先将右节点压栈,再将左节点压栈
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return res;
}
中序遍历
/**
* 实现二叉树的中序遍历。
*
* @param root 二叉树的根节点
* @return
*/
public static List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
inorderDP(root, res);
return res;
}
public static void inorderDP(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
inorderDP(root.left, res);
res.add(root.val);
inorderDP(root.right, res);
}
/**
* 中序遍历 左 中 右
* 入栈顺序 左 右
*
* @param root
* @return
*/
public static List<Integer> inorder(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
cur = stack.pop();
res.add(cur.val);
cur = cur.right;
}
}
return res;
}
后序遍历
/**
* 实现二叉树的后序遍历。
*
* @param root 二叉树的根节点
* @return
*/
public static List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postorderDP(root, res);
return res;
}
public static void postorderDP(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
postorderDP(root.left, res);
postorderDP(root.right, res);
res.add(root.val);
}
/**
* 后序遍历 左 右 中
* 入栈顺序 中 左 右
* 出栈顺序 中 右 左
* 集合反转 左 右 中
*
* @param root
* @return
*/
public static List<Integer> postorder(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
if (node.left != null) {
stack.push(node.left);
}
if (node.right != null) {
stack.push(node.right);
}
}
Collections.reverse(res);
return res;
}
层序遍历
/**
* 实现二叉树的层序遍历。
*
* @param root 二叉树的根节点
* @return
*/
public static List<Integer> levelOrderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
TreeNode node = queue.poll();
res.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return res;
}
二叉树题目
题目:104. 二叉树的最大深度
即求根节点的高度
/**
* @param root 二叉树的根节点
* @return 计算二叉树的最大深度
*/
public static int maxDeep(TreeNode root) {
//后序遍历
if (root == null) {
return 0;
}
int leftDeep = maxDeep(root.left);//左
int rightDeep = maxDeep(root.right);//右
return Math.max(leftDeep, rightDeep) + 1;//中
}
public static int deep = 0;
public static int maxDeepByLevel(TreeNode root) {
if (root == null) {
return 0;
}
//层序遍历
level(root, 0);
return deep;
}
public static void level(TreeNode root, int index) {
//递归的遍历左右子树的最大深度
//到达叶子节点后,更新深度deep
if (root == null) {
deep = Math.max(deep, index);
return;
}
index++;
level(root.left, index);
level(root.right, index);
}
最小深度
求根节点的最小深度,注意如果根节点的左右子树任意为null,则返回的是1,如下图左侧计算
/**
* @param root 二叉树的根节点
* @return 计算二叉树的最小深度,注意如果根节点的左右子树任意为null,则返回的是1
*/
public static int minDeep(TreeNode root) {
if (root == null) {
return 0;
}
int leftDeep = maxDeep(root.left);
int rightDeep = maxDeep(root.right);
return Math.min(leftDeep, rightDeep) + 1;
}
题目:111. 二叉树的最小深度
题目中的最小深度指的是:从根节点到最近叶子节点的最短路径上的节点数量。如上图右侧计算
思路:
分别计算左子树和右子树的最大深度,即leftDeep
和rightDeep
。如果左子节点为空而右子节点不为空,说明根节点的左子树为空,那么最小深度就是右子树的最大深度加1。同样,如果右子节点为空而左子节点不为空,最小深度就是左子树的最大深度加1。
最后,函数返回1加上左右子树中的最小深度,即1 +
Math.min(leftDeep, rightDeep)
/**
* @param root 二叉树的根节点
* @return 计算二叉树的最小深度,排除如果根节点的左右子树任意为null,则返回的是1的情况
* 即从根节点到最浅叶子节点的最小深度
*/
public static int minDeepLogin(TreeNode root) {
if (root == null) {
return 0;
}
int leftDeep = maxDeep(root.left);
int rightDeep = maxDeep(root.right);
if (root.left == null && root.right != null) {
return rightDeep + 1;
}
if (root.right == null && root.left != null) {
return leftDeep + 1;
}
return 1 + Math.min(leftDeep, rightDeep);
}