系列博客目录
基础知识
在 Java 中,二叉树(Binary Tree)是一种常见的树形数据结构,它由一个节点组成,节点具有最多两个子节点,通常称为左子节点和右子节点。二叉树在计算机科学中广泛用于表示数据、优化搜索操作、实现排序算法等。
以下是二叉树的相关理论内容:
1. 二叉树的基本定义
-
节点:二叉树中的基本元素,每个节点包含:
- 一个值(或数据)
- 一个指向左子节点的引用
- 一个指向右子节点的引用
-
根节点:二叉树的第一个节点,也就是最上层的节点。每棵树只有一个根节点。
-
子树:树中的任意节点及其所有后代节点称为该节点的子树。每个节点的左子树和右子树本身也是二叉树。
-
叶子节点:没有子节点的节点称为叶子节点。叶子节点是二叉树的终端节点。
-
深度(Depth):节点的深度是指该节点从根节点开始的路径长度。根节点的深度为 0。
-
高度(Height):树的高度是根节点的深度。一个节点的高度是指从该节点到其所有子节点的最大深度。
2. 二叉树的性质
- 每个节点最多有两个子节点:每个节点最多有一个左子节点和一个右子节点。
- 最多有
2
d
2^d
2d 个节点:如果二叉树的高度为
d
,那么在最坏情况下(即完全二叉树),二叉树的节点数最多为 2 d − 1 2^d-1 2d−1。 - 二叉树的节点总数与高度的关系:
- 对于一个完全二叉树,如果有
n
个节点,树的高度大约是log₂n
。
- 对于一个完全二叉树,如果有
3. 二叉树的类型
-
满二叉树:一棵二叉树如果每个非叶子节点都有左右两个子节点,并且所有叶子节点都在同一层上,那么这棵树是满二叉树。
-
完全二叉树:一棵二叉树如果除了最后一层外,其他层都已经完全填满,且最后一层的叶子节点都靠左排列,那么它是完全二叉树。
-
平衡二叉树(AVL树):一棵二叉树如果它的左右子树高度差的绝对值不超过 1,那么它是平衡二叉树。
-
二叉搜索树(BST,Binary Search Tree):一种特殊的二叉树,具有以下特点:
- 左子树的所有节点的值小于根节点的值。
- 右子树的所有节点的值大于根节点的值。
- 每个节点的左子树和右子树也是二叉搜索树。
-
红黑树:一种自平衡的二叉搜索树,其中每个节点都包含一个颜色属性(红色或黑色),通过一定的规则来保持树的平衡。
4. 二叉树的遍历
遍历是指访问二叉树的所有节点,通常有以下几种方式:
-
前序遍历(Preorder Traversal):访问根节点 -> 遍历左子树 -> 遍历右子树。
- 递归实现:
void preorder(TreeNode root) { if (root == null) return; System.out.print(root.val + " "); preorder(root.left); preorder(root.right); }
- 递归实现:
-
中序遍历(Inorder Traversal):遍历左子树 -> 访问根节点 -> 遍历右子树。对于二叉搜索树,中序遍历的结果是有序的。
- 递归实现:
void inorder(TreeNode root) { if (root == null) return; inorder(root.left); System.out.print(root.val + " "); inorder(root.right); }
- 迭代实现:
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Deque<TreeNode> treeNodes = new LinkedList<>(); while(root!=null || !treeNodes.isEmpty()){ while(root!=null){ treeNodes.push(root); root = root.left; } final TreeNode pop = treeNodes.poll(); res.add(pop.val); root = pop.right; } return res; } }
- 递归实现:
-
后序遍历(Postorder Traversal):遍历左子树 -> 遍历右子树 -> 访问根节点。
- 递归实现:
void postorder(TreeNode root) { if (root == null) return; postorder(root.left); postorder(root.right); System.out.print(root.val + " "); }
- 递归实现:
-
层序遍历(Level-order Traversal):按照从上到下、从左到右的顺序逐层访问树的节点。通常使用队列(Queue)实现。
- 队列实现:
void levelOrder(TreeNode root) { if (root == null) return; Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { TreeNode node = queue.poll(); System.out.print(node.val + " "); if (node.left != null) queue.offer(node.left); if (node.right != null) queue.offer(node.right); } }
- 队列实现:
5. 二叉树的实现
二叉树通常使用节点类(TreeNode
)来实现,节点类包括值、左子节点和右子节点的引用。以下是一个简单的二叉树节点类:
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
this.left = null;
this.right = null;
}
}
6. 常见的二叉树算法
-
查找最大深度(高度):递归计算树的最大深度。
int maxDepth(TreeNode root) { if (root == null) return 0; int leftDepth = maxDepth(root.left); int rightDepth = maxDepth(root.right); return Math.max(leftDepth, rightDepth) + 1; }
-
查找最小深度:与最大深度类似,但返回的是树中最短的路径。
int minDepth(TreeNode root) { if (root == null) return 0; if (root.left == null) return minDepth(root.right) + 1; if (root.right == null) return minDepth(root.left) + 1; return Math.min(minDepth(root.left), minDepth(root.right)) + 1; }
-
判断二叉树是否对称:检查二叉树是否是镜像对称的(即左右子树镜像)。
boolean isSymmetric(TreeNode root) { if (root == null) return true; return isMirror(root.left, root.right); } boolean isMirror(TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null) return true; if (t1 == null || t2 == null) return false; return (t1.val == t2.val) && isMirror(t1.left, t2.right) && isMirror(t1.right, t2.left); }
-
判断二叉搜索树:检查二叉树是否满足二叉搜索树的条件(左子树的值小于根,右子树的值大于根)。
boolean isValidBST(TreeNode root) { return isValidBSTHelper(root, Long.MIN_VALUE, Long.MAX_VALUE); } boolean isValidBSTHelper(TreeNode node, long min, long max) { if (node == null) return true; if (node.val <= min || node.val >= max) return false; return isValidBSTHelper(node.left, min, node.val) && isValidBSTHelper(node.right, node.val, max); }
7. 二叉树的应用
- 堆(Heap):一种完全二叉树,用于实现优先队列。最小堆和最大堆都是基于二叉树的结构。
- 前缀表达式和后缀表达式:二叉树可用于解析和计算前缀或后缀表达式。
- 最短路径和最小生成树:二叉树的变种,如哈夫曼树,可用于数据压缩。
总结
二叉树是一种非常重要的数据结构,具有许多不同的变种和应用。它可以通过递归方法非常方便地进行遍历和操作,并且广泛用于解决各种算法问题,如查找、排序、路径查找等。
例题
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;
}
}
104.二叉树的最大深度
class Solution {
public int maxDepth(TreeNode root) {
return root == null?0:Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}
226.翻转二叉树
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return null;
final TreeNode r = invertTree(root.left);
final TreeNode l = invertTree(root.right);
root.right = r;
root.left = l;
return root;
}
}
101.对称二叉树
链接
self
class Solution {
public boolean isSymmetric(TreeNode root) {
return isSy(root.left,root.right);
}
private boolean isSy(TreeNode left, TreeNode right){
if(left == null && right == null){
return true;
} else if (left != null && right != null) {
if(left.val != right.val){
return false;
}
if(!isSy(left.left,right.right)){
return false;
}
if(!isSy(left.right,right.left)){
return false;
}
}else {//一个为null 一个不为null
return false;
}
return true;
}
}
class Solution {
public boolean isSymmetric(TreeNode root) {
return check(root.left, root.right);
}
public boolean check(TreeNode p, TreeNode q) {
if (p == null && q == null) {
return true;
}
if (p == null || q == null) {
return false;
}
return p.val == q.val && check(p.left, q.right) && check(p.right, q.left);
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/symmetric-tree/solutions/268109/dui-cheng-er-cha-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
100.相同的树
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null) return true;
if(p == null || q == null) return false;
return p.val == q.val && isSameTree(p.left , q.left) && isSameTree(p.right,q.right);
}
}
102.二叉树的层序遍历
链接
思路:我们需要记录下每一层最后一个节点。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
List<Integer> line = new ArrayList<>();
Deque<TreeNode> deque = new LinkedList<>();
TreeNode nextLast = root;
TreeNode currLast = root;
deque.addLast(root);
while(!deque.isEmpty()){
final TreeNode cur = deque.pollFirst();
line.add(cur.val);
if(cur.left!=null){
deque.addLast(cur.left);
nextLast = cur.left;
}
if(cur.right != null){
deque.addLast(cur.right);
nextLast = cur.right;
}
if(cur == currLast){
res.add(line);
line = new ArrayList<>();
currLast = nextLast;
}
}
return res;
}
}
98.验证二叉搜索树
链接
思路,我们需要针对每棵树知道两个子树的最大值最小值和他们两个是否满足二叉搜索树来进行先判断现在两棵子树的最大值和最小值是否与根节点满足二叉搜索树的规定,然后进行递归。实现从下往上判断题目给定的树是否是二叉搜索树。
class Solution {
class NodeRes{
public int max;
public int min;
public boolean valid;
public NodeRes(boolean valid) {
this.valid = valid;
}
}
public boolean isValidBST(TreeNode root) {
if(root == null){
return true;
}
return process(root).valid;
}
public NodeRes process(TreeNode root){
if(root == null) return null;//返回Null不是说二叉搜索树是null,而是说不用为了判断以这个节点为根节点的树而新建一个NodeRes节点。
final NodeRes left = process(root.left);
final NodeRes right = process(root.right);
int min = root.val;
int max = root.val;
if(left != null){
if(!left.valid){
return left;
}
if(left.max >= root.val){
return new NodeRes(false);
}
min = Math.min(min,left.min);
}
if(right != null){
if(!right.valid){
return right;
}
if(right.min <= root.val ){
return new NodeRes(false);
}
max = Math.max(max, right.max);
}
NodeRes res = new NodeRes(true);
res.max = max;
res.min = min;
return res;
}
}
105.从前序与中序遍历序列构造二叉树
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null||preorder.length == 0){
return null;
}
if(preorder.length == 1){
return new TreeNode(preorder[0]);
}
int root = preorder[0];
final int rootOf = indexOf(inorder, root);
final int[] preorderLeft = Arrays.copyOfRange(preorder, 1, rootOf + 1);
final int[] inorderLeft = Arrays.copyOfRange(inorder, 0 , rootOf);
TreeNode left = buildTree(preorderLeft, inorderLeft);
final int[] preorderRight = Arrays.copyOfRange(preorder, rootOf + 1, preorder.length);
final int[] inorderRight = Arrays.copyOfRange(inorder, rootOf + 1, inorder.length);
TreeNode right = buildTree(preorderRight, inorderRight);
return new TreeNode(root,left,right);
}
private int indexOf(int[] nums, int target){
for (int i = 0; i < nums.length; i++) {
if(nums[i] == target) return i;
}
return -1;
}
}
106.从中序与后序遍历序列构造二叉树
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(postorder == null||postorder.length == 0){
return null;
}
if(postorder.length == 1){
return new TreeNode(postorder[0]);
}
int root = postorder[postorder.length - 1];
final int rootOf = indexOf(inorder, root);
final int[] postorderLeft = Arrays.copyOfRange(postorder, 0, rootOf);
final int[] inorderLeft = Arrays.copyOfRange(inorder, 0 , rootOf);
TreeNode left = buildTree(inorderLeft, postorderLeft);
final int[] postorderRight = Arrays.copyOfRange(postorder, rootOf, postorder.length - 1);
final int[] inorderRight = Arrays.copyOfRange(inorder, rootOf + 1, inorder.length);
TreeNode right = buildTree(inorderRight, postorderRight);
return new TreeNode(root,left,right);
}
private int indexOf(int[] nums, int target){
for (int i = 0; i < nums.length; i++) {
if(nums[i] == target) return i;
}
return -1;
}
}
117.填充每个节点的下一个右侧节点指针 II
class Solution {
public Node connect(Node root) {
if(root == null) return null;
Deque<Node> queue = new ArrayDeque<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
Node last = null;
for (int i = 1; i <= size; i++) {
Node f = queue.poll();
if(f.left!=null){
queue.offer(f.left);
}
if(f.right != null) {
queue.offer(f.right);
}
if (i != 1) {
last.next = f;
}
last = f;
}
}
return root;
}
}
114.二叉树展开为链表
思路是:因为要满足前序遍历顺序,所以我们要通过把针对一个树,链表顺序为:根节点,左子树,右子树,我们使用递归来解决。
class Solution {
public void flatten(TreeNode root) {
if(root == null) return;
flatten(root.left);
flatten(root.right);//此时左右子树都为链表
TreeNode r = root.right;//保存右子树,之后接上
root.right = root.left;//把左子树现在已为链表接到root右边
root.left = null;
while(root.right != null){
root = root.right;//找到链表最下面的节点,接上之前存的右子树的链表
}
root.right = r;
}
}
129.求根节点到叶节点数字之和
链接
self思路:想到了要每层根据高度乘10,但是没有官方思路那么清晰。这道题中,二叉树的每条从根节点到叶子节点的路径都代表一个数字。其实,每个节点都对应一个数字,等于其父节点对应的数字乘以 10 再加上该节点的值(这里假设根节点的父节点对应的数字是 0)。只要计算出每个叶子节点对应的数字,然后计算所有叶子节点对应的数字之和,即可得到结果。可以通过深度优先搜索和广度优先搜索实现。下面使用前者。
class Solution {
public int sumNumbers(TreeNode root) {
return dfs(root, 0);
}
public int dfs(TreeNode root, int prevSum) {
if (root == null) {
return 0;//返回0 而不是 prevSum,因为这个点不存在,不应该有值。
}
int sum = prevSum * 10 + root.val;
if (root.left == null && root.right == null) {
return sum;
} else {
return dfs(root.left, sum) + dfs(root.right, sum);
}
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/sum-root-to-leaf-numbers/solutions/464666/qiu-gen-dao-xie-zi-jie-dian-shu-zi-zhi-he-by-leetc/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
236.二叉树的最近公共祖先
链接
也就是说 我们通过递归,找到凑齐两个true的节点。一个true代表这个节点的左右子树中含有q或者p其中一个节点。
class Solution {
private TreeNode ans;
public Solution() {
this.ans = null;
}
private boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) return false;
boolean lson = dfs(root.left, p, q);
boolean rson = dfs(root.right, p, q);
if ((lson && rson) || ((root.val == p.val || root.val == q.val) && (lson || rson))) {
ans = root;
}
return lson || rson || (root.val == p.val || root.val == q.val);
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
this.dfs(root, p, q);
return this.ans;
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/solutions/238552/er-cha-shu-de-zui-jin-gong-gong-zu-xian-by-leetc-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
112.路径总和
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
return process(root,targetSum);
}
private boolean process(TreeNode root, int targetSum){
if(root == null) return false;
if(root.val == targetSum && root.left==null&& root.right ==null) return true;
return process(root.left,targetSum -root.val) || process(root.right,targetSum -root.val);
}
}
222.完全二叉树的节点个数
class Solution {
public int countNodes(TreeNode root) {
if(root == null) return 0;
return countNodes(root.left) + countNodes(root.right) + 1;
}
}
199.二叉树的右视图
链接
借鉴之前的二叉树层序遍历
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Deque<TreeNode> queue = new ArrayDeque<>();
queue.offerLast(root);
TreeNode nextLast = root;
TreeNode currLast = root;
while(!queue.isEmpty()){
TreeNode curr = queue.pollFirst();
if(curr.left != null){
queue.offerLast(curr.left);
nextLast = curr.left;
}
if(curr.right != null){
queue.offerLast(curr.right);
nextLast = curr.right;
}
if(currLast == curr){
res.add(curr.val);
currLast = nextLast;
}
}
return res;
}
}
103.二叉树的锯齿形层序遍历
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
List<Integer> line = new ArrayList<>();
Deque<TreeNode> queue = new ArrayDeque<>();
queue.offerLast(root);
TreeNode nextLast = root;
int index = 0;
TreeNode currLast = root;
while(!queue.isEmpty()){
TreeNode curr = queue.pollFirst();
line.add(curr.val);
if(curr.left != null){
queue.offerLast(curr.left);
nextLast = curr.left;
}
if(curr.right != null){
queue.offerLast(curr.right);
nextLast = curr.right;
}
if(currLast == curr){
if(index == 0){
res.add(line);
}else {
Collections.reverse(line);
res.add(line);
}
index = (index== 0?1:0);
line = new ArrayList<>();
currLast = nextLast;
}
}
return res;
}
}
230.二叉搜索树中第K 小的元素
链接
self思路:用中序遍历得到二叉搜索树的所有元素,其是排好序的,再取出第k个即可。
class Solution {
public int kthSmallest(TreeNode root, int k) {
List<Integer> res = new ArrayList<>();
kth(root,res);
return (int) res.toArray()[k-1];
}
private void kth(TreeNode root,List<Integer> res){
if(root == null) return;
kth(root.left,res);
res.add(root.val);
kth(root.right,res);
}
}
637.二叉树的层平均值
链接
self思路:借鉴层序遍历
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> res = new ArrayList<>();
if(root == null) return res;
List<Integer> line = new ArrayList<>();
Deque<TreeNode> queue = new ArrayDeque<>();
queue.offerLast(root);
TreeNode nextLast = root;
TreeNode currLast = root;
while(!queue.isEmpty()){
TreeNode curr = queue.pollFirst();
line.add(curr.val);
if(curr.left != null){
queue.offerLast(curr.left);
nextLast = curr.left;
}
if(curr.right != null){
queue.offerLast(curr.right);
nextLast = curr.right;
}
if(currLast == curr){
double sum = 0;//不能是 int sum;
for (int num : line) {
sum += num;
}
double average = sum / line.size();
res.add(average);
line = new ArrayList<>();
currLast = nextLast;
}
}
return res;
}
}
530.二叉搜索树的最小绝对差
链接
self思路:先得到树节点的值所组成的排好序的List。
class Solution {
public int getMinimumDifference(TreeNode root) {
List<Integer> res = new ArrayList<>();
kth(root,res);
int min = Integer.MAX_VALUE;
for (int i = 1; i < res.size(); i++) {
min = Math.min(min,res.get(i)-res.get(i-1) );
}
return min;
}
private void kth(TreeNode root,List<Integer> res){
if(root == null) return;
kth(root.left,res);
res.add(root.val);
kth(root.right,res);
}
}