数据结构(七)-树
参考文献
理论
二叉树的高度
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始),而根节点的高度就是二叉树的最大深度
1 | 1 |
二叉树的深度
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
1 | 1 |
满二叉树
- 满二叉树是一种特殊的二叉树,除了叶子节点,每个节点都有左右两个子节点,并且所有叶子节点都在同一层上.也就是说,满二叉树的深度为h,它的节点数为.
完全二叉树
- 完全二叉树是一种特殊的二叉树,除了最后一层之外,其他层的节点数都是满的,并且最后一层的节点都集中在左侧.也就是说,如果最后一层的节点数为m,那么前面的所有层都是满的,并且节点数为,总共的节点数为.
- 需要注意的是,满二叉树一定是完全二叉树,但完全二叉树不一定是满二叉树.
二叉搜索树
- 二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树,它的每个节点都包含一个键值,且满足以下性质:
- 左子树中所有节点的键值都小于当前节点的键值;
- 右子树中所有节点的键值都大于当前节点的键值;
- 左右子树都是二叉搜索树(递归定义)
- 由于满足以上性质,二叉搜索树支持快速插入、删除和查找操作.对于任意节点,它的左子树中所有节点的键值都小于该节点的键值,右子树中所有节点的键值都大于该节点的键值,这样就可以用二分查找的方式快速定位某个节点.
- 在二叉搜索树中,插入和删除操作需要保持树的有序性质,即插入节点时需要找到正确的位置,删除节点时需要保持左右子树仍然是二叉搜索树,并且保持树的平衡性.
- 当二叉搜索树的左右子树分布不均时,可能会导致树的高度很大,从而影响查找效率.为了解决这个问题,可以使用平衡二叉搜索树,例如AVL树、红黑树等,来保持树的平衡性和性能.
平衡二叉搜索树
-
平衡二叉搜索树(Balanced Binary Search Tree)是指一类特殊的二叉搜索树,它能够自动调整树的形态,使得树的高度保持在一个较小的范围内,进而保证查找、插入、删除等操作的效率.常见的平衡二叉搜索树包括 AVL 树、红黑树、B 树等.
-
在平衡二叉搜索树中,每个节点都有一个平衡因子,通常是左子树的高度减去右子树的高度.如果平衡因子的绝对值超过了1,就需要对该节点的子树进行旋转操作,以调整树的形态,使其更加平衡.
-
旋转操作分为左旋和右旋两种,左旋是指将节点的右子树提升到该节点的位置,同时该节点成为左子节点的右子节点,右旋是指将节点的左子树提升到该节点的位置,同时该节点成为右子节点的左子节点.通过旋转操作,可以使得树的高度保持在一个较小的范围内,从而保证了查找、插入、删除等操作的性能.
-
平衡二叉搜索树在实际应用中有广泛的应用,例如数据库索引、编译器中的符号表、路由表等.常见的平衡二叉搜索树有 AVL 树、红黑树、B 树等,每种平衡二叉搜索树都有其特点和适用场景.
-
在 Java 集合类中,有两种类实现了平衡二叉树.
-
TreeMap 类:TreeMap 类实现了
SortedMap
接口,底层使用红黑树(Red-Black Tree)来实现,可以保证元素按照键的自然顺序进行排序.TreeMap 中的每个节点都包含了一个键值对,其中键是唯一的,值可以重复. -
TreeSet 类:TreeSet 类实现了
SortedSet
接口,底层使用红黑树(Red-Black Tree)来实现,可以保证元素按照自然顺序进行排序.TreeSet 中的元素都是唯一的,不允许重复.
-
二叉树的存储方式
- 链式存储:每个节点保存指向其左子树和右子树的指针(或引用).这种存储方式适合于动态变化的二叉树,但是需要额外的空间来存储指针.
- 数组存储:使用数组来存储二叉树的节点,按照某种顺序将节点存储在数组中,并使用数组下标来表示节点的位置.这种存储方式可以节省空间,但是不适合动态变化的二叉树.
二叉树的遍历
-
二叉树主要有两种遍历方式:
-
深度优先遍历(
Depth-First Search,DFS
):先往深走,遇到叶子节点再往回走.-
前序遍历(递归法,迭代法)
- 中左右
-
中序遍历(递归法,迭代法)
- 左中右
-
后序遍历(递归法,迭代法)
- 左右中
-
-
广度优先遍历
Breadth-First Search,BST
:一层一层的去遍历.-
层次遍历(迭代法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<Integer>();
int len = que.size();
while (len > 0) {
TreeNode tmpNode = que.poll();
itemList.add(tmpNode.val);
if (tmpNode.left != null) {
que.offer(tmpNode.left);
}
if (tmpNode.right != null) {
que.offer(tmpNode.right);
}
len--;
}
result.add(itemList);
}
return result;
}
-
递归算法的三个要素.每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型.
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出.
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息.在这里也就会重复调用自己来实现递归的过程.
-
各种树的实现
二分搜索树(BST)
- 二分搜索树: Binary Search Tree
1 | package com.holelin.tree; |
测试用例
1 | package com.holelin.tree; |
平衡二叉树(AVL树)
1 | package com.holelin.tree; |
测试用例
1 | package com.holelin.tree; |
红黑树
1 | package com.holelin.tree; |
测试用例
1 | package com.holelin.tree; |
基于数组的线段树
1 | package com.holelin.tree; |
测试用例
- Merger
1 | package com.holelin.tree; |
1 | package com.holelin.tree; |
字典树(前缀树)
1 | package com.holelin.tree; |
测试用例
1 | package com.holelin.tree; |