第三章 查找(三) - 《算法》读书笔记
第三章 查找(三)
3.3 平衡查找树
3.3.1 2-3查找树
定义。一棵2-3查找树或为一棵空树,或由以下结点组成:
- 2-结点,含有一个键(及其对应的值)和两条链接,左链接指向的2-3树中的键都小于该结点,右链接指向的2-3树中的键都大于该结点。
- 3-结点,含有两个键(及其对应的值)和三条链接,左链接指向的2-3树中的键都小于该结点,中链接指向的2-3树中的键都位于该结点的两个键之间,右链接指向的2-3树中的键都大于该结点。
和以前一样,我们将指向一颗空树的连接称为空链接。
- 一棵完美平衡的2-3查找树中的所有空链接到根结点的距离都应该是相同的。
3.3.1.2 向2-结点中插入新键
- 和二叉查找树一样,先进行一次未命中的查找,然后把新节点挂在树的底部
- 如果未命中的查找结束于一个2-结点,我们只需要把这个2-结点替换为一个3-结点,将要插入的键保存在其中即可
3.3.1.3 向一棵只含有一个3-结点的树中插入新键
- 我们先临时将新键存入该结点中,使之成为一个4-结点
- 再将它转换为一棵由3个2-结点组成的2-3树,其中一个结点(根)含有中键,一个结点含有3个键中的最小者(和根结点的左链接相连),一个结点含有3个键中的最大者(和根结点的右链接相连)
3.3.1.4 向一个父结点为2-结点的3-结点中插入新键
- 同样地,先构造一个临时的4-结点并将其分解
- 将指向原3-结点的一条链接替换为新父结点中的原中键左右两边两条连接,并分别指向两个新的2-结点
3.3.1.5 向一个父结点为3-结点的3-结点中插入新键
- 构造4-结点并分解,将它的中键插入它的父结点中
- 在父结点进行相同的变换
3.3.1.6 分解根结点
- 按照向一棵只含有一个3-结点的树中插入新键的方法来处理,使树高加1
3.3.1.8 全局性质
在一颗大小为N的2-3树中,查找和插入操作访问的结点必然不超过lgN个。
3.3.2 红黑二叉查找树
3.3.2.1 替换3-结点
- 我们将树中的链接分为两种类型:
- 红链接将两个2-结点连接起来构成一个3-结点
- 黑链接则是2-3树中的普通链接
3.3.2.2 一种等价的定义
- 红黑树的另一种定义时含有红黑链接并满足下列条件的二叉查找树:
- 红链接均为左链接
- 没有任何一个结点同时和两条红链接相连
- 该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同
- 满足这样定义的红黑树和相应的2-3树是一一对应的
3.3.2.4 颜色表示
- 因为每个结点都只会有一条指向自己的链接(从它的父结点指向它),我们将链接的颜色保存在表示结点的Node()数据类型的布尔变量color中,红色为true,黑色为false,约定空链接为黑色。
3.3.2.5 旋转
- 旋转操作会改变红链接的指向
- 左旋转:将红色的右链接转化为左链接,实现如下
Node rotateLeft(Node h){
//h为红链接左端,x为红链接右端,需要将h作为根结点变为x作为根结点
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color;
h.color = RED;
x.N = h.N;
h.N = 1 + size(h.left) + size(h.right);
return x;
}
- 右结点类似
3.3.2.6 在旋转后重置父结点的链接
- 旋转操作可以保持红黑树的两个重要性质:有序性和完美平衡性
3.3.2.7 向单个2-结点中插入新键
- 如果新键小于老键,我们只需要新增一个红色的结点即可,新的红黑树和单个2-3结点完全等价
- 如果新键大于老键,那么新增的红色结点将会产生一条红色的右链接,我们需要使用
root=rotateLeft(root)来将其旋转为红色左链接,并修正根结点的链接,插入操作才算完成
3.3.2.8 向树底部的2-结点插入新键
- 在树的底部新增一个结点,用红链接将新结点和父结点相连
- 如果它的父结点是一个2-结点,那么上面的两种方法仍然适用
3.3.2.9 向一棵双键树(即一个3-结点)中插入新键
- 如果新键大于原树中的两个键,则被连接到3-结点的右链接,再将两条链接的颜色都由红变黑即可
- 如果新键小于原树中的两个键,则被连接到最左边的空链接,这样就产生了两条连续的红链接。此时只需将上层的红链接右旋转,即可得到第一种情况
- 如果新键介于原树中的两个键之间,也会产生两条连续的红链接,一条红色左链接,一条红色右链接。此时只需将下层的红链接左旋转,即可得到第二种情况
3.3.2.10 颜色转换
- 我们专门用一个方法flipColors()来转换一个结点的两个红色子结点的颜色
- 除了将子结点的颜色由红变黑外,我们还要将父结点的颜色由黑变红,对应2-3树中,将中键插入父结点
3.3.2.11 根结点总是黑色
- 每次插入后都会将根结点设为黑色
3.3.2.12 向树底部的3-结点插入新键
- 颜色转换会使到中结点的颜色变红,相当于将它送入了父结点,这也意味着在父结点中继续插入一个新键,我们也会用相同的办法解决这个问题
3.3.2.13 将红链接在树中向上传递
- 在沿着插入点到根结点的路径向上移动时在所经过的每个结点中顺序完成以下操作,我们就能完成插入操作:
- 如果右子结点是红色的,而左子结点是红色的,进行左旋转
- 如果左子结点是红色的,且它的左子结点也是红色的,进行右旋转
- 如果左右子结点均为红色,进行颜色转换
3.3.3 实现
public class RedBlackBST<Key extends Comparable<Key>, Value>{
private Node root;
private class Node{
...
}
private boolean isRed(Node h);
private Node rotateLeft(Node h);
private Node rotateRight(Node h);
private void flipColors(Node h);
private int size();
public void put(Key key, Value, val){
root = put(root, key, val);
root.color = BLACK;
}
public Node put(Node h, Key key, Value val){
if(h == null) return new Node(key, val, 1, RED);
int cmp = key.compareTo(h.key);
if(cmp < 0) h.left = put(h.left, key, val);
else if(cmp > 0) h.right = put(h.right, key, val);
else h.val = val;
if(isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);
if(isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
if(isRed(h.left) && isRed(h.right)) flipColors(h);
h.N = size(h.left) + size(h.right) + 1;
return h;
}
}
3.3.4 删除操作
3.3.4.1 自顶向下的2-3-4树
- 它的插入算法沿差错路径向下进行变换是为了保证当前节点不是4-节点(这样树底才有空间来插入新的键),沿查找路径向上进行变换是巍峨将之前创建的4-节点进行配平
- 要用红黑树实现这个算法,我们需要:
- 将4-结点表示为由三个2-结点组成的一颗平衡的子树,根结点和两个子结点都用红链接相连
- 在向下的过程分解所有4-结点并进行颜色转换
- 和插入操作一样,在向上的过程中用旋转将4-结点配平
3.3.4.2 删除最小键
-
在沿着左链接向下的过程中,保证以下情况之一成立:
- 如果当前结点的左子结点不是2-结点,完成
- 如果当前结点的左子结点是2-结点,而它的亲兄弟不是2-结点,将左子结点的兄弟结点中的一个键移动到左子结点中
- 如果当前就结点的左子结点和它的亲兄弟都是2-结点,将左子结点和父结点中的最小键和左子结点最近的兄弟结点合并为一个4-结点,使父结点由3-结点变为2-结点,或者由4-结点变为3-结点
-
最后能够得到承恩含有最小键的3-结点或4-结点,直接将其删除,然后再回头向上分解所有临时的4-结点
3.3.4.3 删除操作
- 如果查找的键不在树的底部,我们需要将它和它的后继结点交换
- 因为当前结点必然不是2-结点,问题就转化为在一棵根结点不是2-结点的子树中删除最小的键
- 我们在子树中使用同样的算法,并在删除之后向上回溯,并分解余下的4-结点
3.3.5 红黑树的性质
- 所有基于红黑树的符号表实现都能保证操作的运行时间为对数级别(范围查找除外,它所需的额外时间和返回的键的数量成正比)
3.3.5.1 性能分析
一棵大小为N的红黑树的高度不会超过2lgN。
一棵大小为N的红黑树中,根结点到任意结点的平均路径长度为~1.00lgN
3.3.5.2 有序符号表API
在一棵红黑树中,以下操作在最坏情况下所需的时间是对数级别的:查找(get())、插入(put())、查找最小键、查找最大键、floor()、ceiling()、rand()、select()、删除最小键(deleteMin())、删除最大键(deleteMax())、删除(delete())和范围查询(range())。
- 各种符号表实现的性能总结:
| 算法(数据结构) | 最坏情况下查找时间 | 最坏情况下插入时间 | 平均情况下查找时间 | 平均情况下插入时间 | 是否支持有序性相关的操作 |
|---|---|---|---|---|---|
| 顺序查询(无序链表) | N | N | N/2 | N | 否 |
| 二分查找(有序数组) | lgN | N | lgN | N/2 | 是 |
| 二叉树查找(BST) | N | N | 1.39lgN | 1.39lgN | 是 |
| 2-3树查找(红黑树) | 2lgN | 2lgN | 1.00lgN | 1.00lgN | 是 |

浙公网安备 33010602011771号