二叉查找树 & 红黑树
//内容来自《算法导论》Chapter 12&13
//算法导论的电子版,在我的下载资源中有,
//附有答案
-----------------------------------------------------
- 二叉查找树性质:
设x为二叉查找树的一个结点。如果y是x的左子树,则key[y] <=key[x]。
如果y是x的右子树中的一个结点,则key[x]<=key[y]。
- 定理12.1
如果x是一棵包含n个结点的子树的根,则调用INORDER-TREE_WALK(x)过程的时间为
O(n)。
- 定理12.2
对一棵高度为h的二叉树,动态集合操作SEARCH, MINIMUM, MAXIMUM, SUCCESSOR,
PREDESSOR等的运行时间均为O(h)。
- 定理12.3
对高度为h的二叉查找树,动态集合操作INSERT和DELETE的运行时间为O(h)。
- 定理12.4
一棵在n个关键字上随机构造的二叉查找树的期望高度为O(lgn)。
- 中序遍历二叉查找树,可以按照排列顺序输出树中的所有关键字。
插入: 结点的插入操作永远在树的“最外层”进行。
删除:
- 红黑树是“平衡的“查找树的一种,它能保证在最坏的情况下,基本动态集合操作的时间
为O(lgn). (search, predecesor, successor, minimum, maximum, inseart, delete)
- 红黑树确保没有一条路径会比其他路径长出两倍,因而是接近“平衡”的。
- 树中每个结点包含五个域:color, key, left, right, p.
- 如果结点没有父结点或者没有子结点,则该结点相应的指针(p)域包含值NIL。
我们把这些nul视为查找树的外结点(叶子)的指针,而把带关键字的结点视为树的内
结点。
- 为了便于处理红黑树代码中的边界条件,我们采用一个哨兵(nil[T])代表NIL。
所有的NIL被替换为指向nil[T]的指针。
- 我们绘的图中忽略了叶子结点。
- 从某个结点x出发(不包括该结点)到达一个叶子结点的任意一条路径上,黑色结点的
个数称为该结点x的黑高度, 用bh(x)表示。红黑树的高度为根结点的黑高度。
--------------------
红黑树的性质:
1. 每个结点或是红色的,或是黑色的
2. 根结点是黑色的
3. 每个叶子结点(NIL)是黑色的
4. 如果一个结点是红色的,则它的两个儿子都是黑色的
5. 对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点
-------------
- 引理13.1 一棵有n个内结点的红黑树的高度至多为2lg(n+1)。
-----
- 当红黑树的性质因为插入或者删除而被破坏,可以通过旋转和调整结点颜色来恢复。
其中旋转可以同时也保证红黑树的二叉查找树的性质。
- 插入: 结点的插入操作永远在树的“最外层”内结点进行。
插入://z是被插入的结点
- 上面的循环的每次迭代都会使得“问题指针”z上移,或者执行了某些旋转操作而结束
循环。
删除:
- 如果被删除的是红色结点,则红黑树的性质仍然保持:
+ 树中各结点的黑高度都没有变化
+ 不存在两个相邻的红色结点
+ 如果被删除的结点是红色的,就不可能是根,所以根仍然是黑色的
//内容来自《算法导论》Chapter 12&13
//算法导论的电子版,在我的下载资源中有,
//附有答案
-----------------------------------------------------
- 二叉查找树性质:
设x为二叉查找树的一个结点。如果y是x的左子树,则key[y] <=key[x]。
如果y是x的右子树中的一个结点,则key[x]<=key[y]。
- 定理12.1
如果x是一棵包含n个结点的子树的根,则调用INORDER-TREE_WALK(x)过程的时间为
O(n)。
- 定理12.2
对一棵高度为h的二叉树,动态集合操作SEARCH, MINIMUM, MAXIMUM, SUCCESSOR,
PREDESSOR等的运行时间均为O(h)。
- 定理12.3
对高度为h的二叉查找树,动态集合操作INSERT和DELETE的运行时间为O(h)。
- 定理12.4
一棵在n个关键字上随机构造的二叉查找树的期望高度为O(lgn)。
- 中序遍历二叉查找树,可以按照排列顺序输出树中的所有关键字。
插入: 结点的插入操作永远在树的“最外层”进行。
删除:
/* 从T中,"删除" z结点 */
TREE-DELETE(T, z)
{
/* y指向实际要删除结点 */
if( left[z] == NIL || right[z] == NIL) { //如果z最多只有一个孩子
y = z;
} else { // 如果z有两个孩子
y = TREE-SUCCESSOR(z);
}
/* x指向实际要删除结点y的一个孩子 */
if(left[y] != NIL) {
x = left[y];
} else { // 注意:当z的左右孩子都不为空时 ,y只有右孩子,没有左孩子
x = right[y];
}
/* 处理实际要删除结点的孩子的父指针 */
if( x != NIL) {//排除了z为叶子结点的情况
p[x] = p[y];//把实际要删除结点的孩子交给它的父亲
}
/* 处理实际要删除结点父亲的孩子指针 */
if (p[y] = NIL) { //实际要删除的是根结点
root[T] = x;
} else if (y == left[p[y]]) {//y为其父结点的左孩子
left[p[y]] = x;
} else { //y为其父结点的右孩子
right[p[y]] = x;
}
if (y != z) {//z的左右孩子都不为空
key[z] = key[y];
copy y's satelite data into z;
}
}
Figure 12-4 删除操作
- 红黑树是“平衡的“查找树的一种,它能保证在最坏的情况下,基本动态集合操作的时间
为O(lgn). (search, predecesor, successor, minimum, maximum, inseart, delete)
- 红黑树确保没有一条路径会比其他路径长出两倍,因而是接近“平衡”的。
- 树中每个结点包含五个域:color, key, left, right, p.
- 如果结点没有父结点或者没有子结点,则该结点相应的指针(p)域包含值NIL。
我们把这些nul视为查找树的外结点(叶子)的指针,而把带关键字的结点视为树的内
结点。
- 为了便于处理红黑树代码中的边界条件,我们采用一个哨兵(nil[T])代表NIL。
所有的NIL被替换为指向nil[T]的指针。
- 我们绘的图中忽略了叶子结点。
- 从某个结点x出发(不包括该结点)到达一个叶子结点的任意一条路径上,黑色结点的
个数称为该结点x的黑高度, 用bh(x)表示。红黑树的高度为根结点的黑高度。
--------------------
红黑树的性质:
1. 每个结点或是红色的,或是黑色的
2. 根结点是黑色的
3. 每个叶子结点(NIL)是黑色的
4. 如果一个结点是红色的,则它的两个儿子都是黑色的
5. 对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点
-------------
- 引理13.1 一棵有n个内结点的红黑树的高度至多为2lg(n+1)。
-----
- 当红黑树的性质因为插入或者删除而被破坏,可以通过旋转和调整结点颜色来恢复。
其中旋转可以同时也保证红黑树的二叉查找树的性质。
- 插入: 结点的插入操作永远在树的“最外层”内结点进行。
插入://z是被插入的结点
RB-INSERT-FIXUP(T, z)
{
y = nil[T];
x = root[T];
/* 找到插入位置, */
while( x != nil[T]) { // y指向z要插入的父结点
y = x;
if (key[z] < key[x]) {
x = left[x];
} else {
x = right[x];
}
} //while
/* 插入 */
p[z] = y;
if (y == nil[T]) {
root[T] = z;
} else if (key[z] < key[y]) {
left[y] =z;
} else {
right[y] = z;
}
left[z] = nil[T];
right[z] = nil[T];
color[z] = RED;
/* 调整 */
RB-INSERT-FIXUP(T, z);
}
RB-INSERT-FIXUP(T, z)
{
while (color[p[z]] == RED){//z的父结点为红色
if (p[z] == left[p[p[z]]]) {//如果z的父结点是z祖父结点的左孩子
y = right[p[p[z]]]; // y指向z祖父结点的右孩子
if (color[y] == RED){//z结点的(右)叔叔结点是红色的 <case 1>
//以下的变换过程保持红黑树的性质
color[p[z]] = BLACK;
color[y] = BLACK;
color[p[p[z]]] = RED;
z = p[p[z]];//“问题指针”z上移
} else if (z == right[p[z]]) { // 如果z是其父结点的右孩子, z的(右)叔叔结点是黑色的<case 2>
/* 转化为左孩子,进入 <case 3>*/
z = p[z]; //“问题指针”z上移
LEFT-ROTATE(T,z);
}
/* 如果z是其父结点的左孩子, z的(右)叔叔结点是黑色的<case 3>*/
color[p[z]] = BLACK;
color[p[p[z]]] = RED;
RIGHT-ROTATE(T, p[p[z]]);
}
} else {//如果z的父结点是z祖父结点的右孩子
same as then clause with "right" and "left" exchange;
}
color[root[T]] = BLACK;// 涂黑根部
}
- 上面的循环的每次迭代都会使得“问题指针”z上移,或者执行了某些旋转操作而结束
循环。
Figure 13-5 && Figure 13-6 插入操作
删除:
/* 从T中,"删除" z结点 */
RB-DELETE(T, z)
{
/* y指向实际要删除结点 */
if( left[z] == nil[T] || right[z] == nil[T]) { //如果z最多只有一个孩子
y = z;
} else { // 如果z有两个孩子
y = TREE-SUCCESSOR(z);
}
/* x指向实际要删除结点y的一个孩子 */
if(left[y] != nil[T]) {
x = left[y];
} else { // 注意:当z的左右孩子都不为空时 ,y只有右孩子,没有左孩子
x = right[y];
}
/* 处理实际要删除结点的孩子的父指针 */
p[x] = p[y];//把实际要删除结点的孩子交给它的父亲
/* 处理实际要删除结点父亲的孩子指针 */
if (p[y] = nil[T]) { //实际要删除的是根结点
root[T] = x;
} else if (y == left[p[y]]) {//y为其父结点的左孩子
left[p[y]] = x;
} else { //y为其父结点的右孩子
right[p[y]] = x;
}
if (y != z) {//z的左右孩子都不为空
key[z] = key[y];
copy y's satelite data into z;
}
if (color[y] == BLACK) {//如果实际删除的是黑色结点,那么需要调整
RB-DELETE-FIXUP(T, x);
}
return y;
}
//注意:x被假想额为多了一层黑色,所有的操作都是为了合理的把这个多出来的
//一重黑色转移到x的父结点
RB-DELETE-FIXUP(T, x)
{
while (x != root[T] && color[x] == BLACK) {
if (x == left[p[x]]) { // 如果x为左孩子
w = right[p[x]];
if (color[w] == RED) { //case 1: 如果兄弟结点为红色, 则
color[p[x]] = RED; // 父结点染为红色
LEFT-ROTATE(T, p[x]);//父结点左旋
w = right[p[x]];//
}
if (color[left[w]]== BLACK && color[right[w]] == BLACK) {//case 2: 如果兄弟结点为黑色,
//而且兄弟结点的两个孩子都是黑色
color[w] = RED;
x = p[x];
} else {
if (color[right[w]] == BLACK) {//case 3: 如果兄弟结点为黑色,兄弟结点的右孩子是黑色、
//左孩子是红色 (-> case 4)
color[left[w]] = BLACK;
color[w] = RED;
RIGHT-ROTATE(T, w);
w = right[p[x]];
}
//case 4: 兄弟结点为黑色,兄弟结点的右孩子是红色
color[w] = color[p[x]];
color[p[x]] = BLACK;
color[right[w]] = BLACK;
LEFT-ROTATE(T, p[x]);
x = root[T];
}
} else { //如果x为右孩子
same as the clause with "right" and "left" exchanged;
}
color[x] = BLACK;
}
Figure 13-7 删除调整操作
- 如果被删除的是红色结点,则红黑树的性质仍然保持:
+ 树中各结点的黑高度都没有变化
+ 不存在两个相邻的红色结点
+ 如果被删除的结点是红色的,就不可能是根,所以根仍然是黑色的