预备知识
- 二叉查找树、平衡树
- 2-3树
2-3 树
- 2个键 3个结点
- 平衡特性
- 空链接到根结点的距离都相等
- 这个可以通过构造2-3树时的各种插入过程进行理解
- 当根结点分被分解为3个2-结点时,所有的空链接到根结点路径长度才会加1
- 空链接到根结点的距离都相等
- 树的组成
- 2结点
- 1个键,左链子树的值小于该键值,右边子树的值大于该键值
- 3结点
- 2个键,左链子树的值都小于该结点的键值,右链子树的值都大于该结点的键值,中间子树的值位于这两个键值中间
- 2结点
- 无论如何构造,查找效率都是对数级别的
- 二叉查找树,在构造为平衡二叉树的情况下,其查找效率时对数级别的,但是
- 在最坏的情况下,其查找效率可以退缩到线性级别的
- 使用2-3树平衡查找树,便可在任何构造情况下都是对数级别的
- 2-3树的一种实际运用是红黑树,后文将会介绍
2-3树的操作
-
查找
- 从根结点开始,判断键值是否相等,若相等则查找成功,否则根据所在区间在子树中继续查找
- 如果是个空链接,查找未命中
-
插入
- 2-结点插入
- 直接插入
- 只含3-结点
- 先变成一个4结点,然后扩展成一个包含三3个2结点的2-3树
- 此时树依旧平衡,所有空链接到根结点的距离都相等
- 2-结点为父结点的3-结点插入
- 先变成一个4结点,扩展成包含3个2-结点的2-3子树,将子树的根结点与父亲结点合并得到最终的2-3平衡树
- 3-结点为父结点的3-结点插入
- 在要插入的位置扩展成一个4结点,然后分解为包含3个2-结点的2-3子树
- 接着,将子树根结点与父亲3-结点继续扩展和分解,直到遇到一个2结点或者到达根结点
- 分解根结点
- 如果插入的路径上都是3-结点,则最终的根结点变成一个4-结点,此时分解这个4-结点
- 此时与像只含3-结点的插入方法一致,最终树的高度加1,但依旧保持平衡,因为它的变换是根结点
- 总结
- 局部性 除了相关的结点和链接之外不必检查或者修改其他的部分
- 全局性 局部变化,不会影响树的全局有序性和平衡性,因为无论是
- 将一个4-结点分别成2-结点,并将父结点由2-结点变为3-结点
- 还是将3-结点变成一个临时的4-结点
- 或者最终分解了根结点,导致空链接到根结点的路径都加1,都保证了空链接到根结点的距离都相等
- 生长模式: 2-3树由下向上生长,而标准的二叉树,是由上向下生长
- 2-结点插入
2-3树的缺点
- 需要维护两个不同类型的结点
- 2-结点
- 3-结点
- 实现的代码量很大
- 被查找键和结点中的每个键进行比较
- 将链接和其他信息从一种结点复制到另一个结点
- 将结点从一种数据类型转换到另一种数据结构,等等
红黑树 升级版二叉树,可以说是二叉查找树中的钢铁侠
- 主要思想:用标准的二叉树和一些额外的信息来表示2-3树
- 标准二叉查找树,完全由2-结点构成
- 额外的信息,是用来替换3-结点的
- 一种等价的定义如下:
- 红链接均为左链接
- 没有任何一个结点同时和两条红链接相连
- 该树时完美平衡的,即任意空链接到根结点的距离相等
- 红黑树,即是二叉查找树,也是2-3树,使用了各自的优点
- 二叉查找树中简洁高效的查找方法
- 2-3树中高效的平衡插入算法
操作
-
当实现某些操作中可能会出现红色右链接或者两条连续的红链接,需要在操作完成之前进行旋转并修复
-
与构造AVL平衡二叉查找树类似,在构造过程中需要进行旋转变换
- 左旋转 子树根结点的右链接为红色,且左子结点为黑色进行左旋转
- 右旋转 左子结点是红色的且左子结点的左子结点也是红色的,进行右旋转
- 如果左右子结点均为红色,进行颜色转换,当根结点由红变黑时,树的黑链接高度加1(相当于分解根结点了)。
-
插入(新结点插入时,默认为红色,即父结点指向该结点的链接为红色)
- 向单个2-结点中插入
- 向左插入
- 向右插入
- 向树底部的2-结点插入
- 正确三结点
- 错误三结点
- 向一个双键树插入新键
- 新键最大
- 新键最小
- 新键介于两者之间
- 向树底部的3-结点插入新键
- 红键向上传递性
- 向单个2-结点中插入
-
左旋转参考代码
// 右链接为红色,左链接为黑色 进行左旋转
Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color; // h.color 可红可黑
h.color = RED;
x.N = h.N;
H.n = 1 + size(h.left) + size(h.right);
return x;
}
- 右旋转参考代码
// 左子结点是红色的且左子结点的左子结点也是红色的,进行右旋转
Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
x.N = h.N;
h.N = 1 + size(h.left) + size(h.right);
return x;
}
- 颜色转换参考代码
void flipColors(Node h) {
h.color = RED;
h.left.color = BLACK;
h.left.color = BLACK;
}
- 数据结构
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node {
Key key; // 键
Value value; // 键关联的值
Node left;
Node right;
int N; // 这颗子树中的结点总数
boolead color; // 由父结点指向它链接的颜色
Node(Key key, Value val, int N, boolean color) {
this.key = key;
this.value = value;
this.N = N;
this.color color;
}
private boolean isRead(Node x) {
if (x == null) return false;
return x.color == RED;
}
}
红黑树的性质
- 研究红黑树的性质就是要检查对应的2-3树并对应的2-3树进行分析的过程。
- 关于红黑树的几个命题:
- 一棵大小为N的红黑树的高度不会超过2lgN
- 一棵大小为N的红黑树中,跟结点到任意结点的平均长度为~1.00lgN
- 一下操作最坏情况下时对数级别的
- get()
- put()
- floor()
- ceiling()
- rank()
- select()
- deleteMin()
- deleteMax()
- delete()
- range()