红黑树(Red-Black Tree)
红黑树定义
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色。
性质2. 根是黑色。
性质3. 所有叶子都是黑色(叶子是NIL节点)。
性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
下面先给出红黑树的抽象数据类型
#ifndef RBT_H
#define RBT_H
typedef enum {
RBT_STATUS_OK,
RBT_STATUS_MEM_EXHAUSTED,
RBT_STATUS_DUPLICATE_KEY,
RBT_STATUS_KEY_NOT_FOUND
} RbtStatus;
typedef void *RbtIterator;
typedef void *RbtHandle;
RbtHandle rbtNew(int(*compare)(void *a, void *b));
// create red-black tree
// parameters:
// compare pointer to function that compares keys
// return 0 if a == b
// return < 0 if a < b
// return > 0 if a > b
// returns:
// handle use handle in calls to rbt functions
void rbtDelete(RbtHandle h);
// destroy red-black tree
RbtStatus rbtInsert(RbtHandle h, void *key, void *value);
// insert key/value pair
RbtStatus rbtErase(RbtHandle h, RbtIterator i);
// delete node in tree associated with iterator
// this function does not free the key/value pointers
RbtIterator rbtNext(RbtHandle h, RbtIterator i);
// return ++i
RbtIterator rbtBegin(RbtHandle h);
// return pointer to first node
RbtIterator rbtEnd(RbtHandle h);
// return pointer to one past last node
void rbtKeyValue(RbtHandle h, RbtIterator i, void **key, void **value);
// returns key/value pair associated with iterator
RbtIterator rbtFind(RbtHandle h, void *key);
// returns iterator associated with key
#endif
红黑树平衡化旋转
红黑树的旋转操作没有AVL树那么复杂,这里的旋转也不区分结点的颜色,纯粹是旋转移动结点的位置,即只有两种操作——左旋和右旋。
1.左旋
static void rotateLeft(RbtType *rbt, NodeType *x) {
// rotate node x to left
NodeType *y = x->right;
// establish x->right link
x->right = y->left;
if (y->left != SENTINEL) y->left->parent = x;
// establish y->parent link
if (y != SENTINEL) y->parent = x->parent;
if (x->parent) {
if (x == x->parent->left)
x->parent->left = y;
else
x->parent->right = y;
} else {
rbt->root = y;
}
// link x and y
y->left = x;
if (x != SENTINEL) x->parent = y;
}
2.右旋
static void rotateRight(RbtType *rbt, NodeType *x) {
// rotate node x to right
NodeType *y = x->left;
// establish x->left link
x->left = y->right;
if (y->right != SENTINEL) y->right->parent = x;
// establish y->parent link
if (y != SENTINEL) y->parent = x->parent;
if (x->parent) {
if (x == x->parent->right)
x->parent->right = y;
else
x->parent->left = y;
} else {
rbt->root = y;
}
// link x and y
y->right = x;
if (x != SENTINEL) x->parent = y;
}
红黑树的插入和删除
1. 插入操作
我们总是插入红色的节点,因为这样就可以在插入过程中尽量避免产生对树的调整,我们新插入一个节点后可能使原树的哪些性质改变呢。由于我们是按二叉树的方式插入,因此原树的搜索性质不会改变。如果插入的节点是根节点,性质2会被破坏,如果插入的节点的父节点是红色,则会破坏性质4,因此总的来说,插入一个红色节点只会破坏到性质2或是性质4,我们的恢复树性质的策略很简单,其一就是保持一定的性质不变,之后把出现违背红黑树性质地方向上移,如果能移到根节点,那么很容易就可以通过直接修改根节点来恢复红黑树应满足的性质。其二就是穷举所有的可能性,之后把能归于同一类方法处理的归为同一类,不能直接处理的化归到其它可以直接处理的情况,能直接处理的就直接处理。
下面看看一共有几种情况,
情况1:插入的是根节点(原树是空树)
此情况只会违反性质2。
解法:直接把此节点涂为黑色
情况2:插入的节点的的父节点是黑色
此时不会违反性质2也不会违反性质4,红黑树没有被破坏。
解法:什么都不做
情况3:当前节点的父节点是红色且祖父节点的另一个子节点(叔叔节点)是红色(此时父节点的父节点一定存在,否则插入前就已不是红黑树,此时又分为父节点是祖父节点的左子或者右子,对于对称性,只要解开一个方向就可以了,我们只考虑父节点为祖父左子的情况)。此时还可分为当前节点是其父节点的左子还是右子,但是这两种情况的处理方式是一样的,我们将其归为同一类。
解法:将当前节点的父节点和叔叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点,从新的当前节点重新开始算法。
变换前
变换后
情况4:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子
解法:当前节点的父节点做为新的当前节点,以新当前节点为支点左旋。
如下图所示
变换前
变换后
情况5:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子
解法:父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋
如下图所示
变换前
变换后
在上面的插入算法中,所有递归都是尾递归,可以改写为循环,其中以当前节点的父节点是否为红色是十分方便的。
2.删除操作
我们删除的节点的方法与常规二叉搜索树中删除节点的方法是一样的,如果被删除的节点不是有双非空子女,则直接删除这个节点,用它的唯一子节点顶替它的位置,如果它的子节点分是空节点,那就用空节点顶替它的位置,如果它的双子全为非空,我们就把它的直接后继节点内容复制到它的位置,之后以同样的方式删除它的后继节点,它的后继节点不可能是双子非空,因此此传递过程最多只进行一次。在册除节点后,原红黑树的性质可能被改变,如果删除的是红色节点,那么原红黑树的性质依旧保持,此时不用做修正操作,如果删除的节点是黑色节点,原红黑树的性质可能会被改变,我们要对其做修正操作。那么哪些树的性质会发生变化呢,如果删除节点不是树唯一节点,那么删除节点的那一个支的到各叶节点的黑色节点数会发生变化,此时性质5被破坏。如果被删节点的唯物主唯一非空子节点是红色,而被删节点的父节点也是红色,那么性质4被破坏。如果被删节点是根节点,而它的唯一非空子节点是红色,则删除后新根节点将变成红色,违背性质2。现在情况看起来有些复杂,这里面我们用一个分析技巧,这引技巧是从《算法导论》一书中学来的。我们从被删节点后来顶替它的那个节点开始调整,并认为它有额外的一重黑色。这里额外一重黑色是什么意思呢,我们不是把红黑树的节点加上除红与黑的另一种颜色,这里只是一种假设,我们认为我们当前指向它,因此空有额外一种黑色,可以认为它的黑色是从它的父节点被删除后继承给它的,它现在可以容纳两种颜色,如果它原来是红色,那么现在是红+黑,如果原来是黑色,那么它现在的颜色是黑+黑。有了这重额外的黑色,原红黑树性质5就能保持不变。现在只要花时是恢复其它性质就可以了,做法还是尽量向根移动和穷举所有可能性。
情况1:当前节点是红+黑色
解法,直接把当前节点染成黑色,结束。
此时红黑树性质全部恢复。
情况2:当前节点是黑+黑且是根节点
解法:什么都不做,结束
情况3:当前节点是黑+黑且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑)。
解法:把父节点染成红色,把兄弟结点染成黑色,之后重新进入算法(我们只讨论当前节点是其父节点左孩子时的情况)。此变换后原红黑树性质5不变,而把问题转化为兄弟节点为黑色的情况。
变换前
变换后
情况4:当前节点是黑加黑且兄弟是黑色且兄弟节点的两个子节点全为黑色
解法:把当前节点和兄弟节点中抽取一重黑色追加到父节点上,把父节点当成新的当前节点,重新进入算法。(此变换后性质5不变)
变换前
变换后
情况5:当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色。
解法:把兄弟结点染红,兄弟左子节点染黑,之后再在兄弟节点为支点解右旋,之后重新进入算法。此是把当前的情况转化为情况6,而性质5得以保持。
变换前
变换后
情况6:当前节点颜色是黑-黑,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意。
解法:把兄弟节点染成当前节点父节点的颜色,把当前节点父节点染成黑色,兄弟节点右子染成黑色,之后以当前节点的父节点为支点进行左旋,此时算法结束,红黑树所有性质调整正确。
变换后
红黑树的高度分析
包含n个内部节点的红黑树的高度是 O(log(n))。
定义:
- h(v) = 以节点v为根的子树的高度。
- bh(v) = 从v到子树中任何叶子的黑色节点的数目(如果v是黑色则不计数它)(也叫做黑色高度)。
引理: 以节点v为根的子树有至少个内部节点。
引理的证明(通过归纳高度):
基础: h(v) = 0
如果v的高度是零则它必定是 nil,因此 bh(v) = 0。所以:
归纳假设: h(v) = k 的v有 个内部节点暗示了 h(
) = k+1 的
有
个内部节点。
因为 有 h(
) > 0 所以它是个内部节点。同样的它有黑色高度要么是 bh(
) 要么是 bh(
)-1 (依据
是红色还是黑色)的两个儿子。通过归纳假设每个儿子都有至少
个内部接点,所以
有:
个内部节点。
使用这个引理我们现在可以展示出树的高度是对数性的。因为在从根到叶子的任何路径上至少有一半的节点是黑色(根据红黑树属性4),根的黑色高度至少是h(root)/2。通过引理我们得到:
因此根的高度是O(log(n))。
下面附上完整的实现代码
// red-black tree
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
//////////////////////
// supplied by user //
//////////////////////
typedef int KeyType; // type of key
typedef struct { // value related to key
int stuff;
} ValType;
// how to compare keys
#define compLT(a,b) (a < b)
#define compEQ(a,b) (a == b)
/////////////////////////////////////
// implementation independent code //
/////////////////////////////////////
typedef enum {
RBT_STATUS_OK,
RBT_STATUS_MEM_EXHAUSTED,
RBT_STATUS_DUPLICATE_KEY,
RBT_STATUS_KEY_NOT_FOUND
} RbtStatus;
typedef enum { BLACK, RED } nodeColor;
typedef struct NodeTag {
struct NodeTag *left; // left child
struct NodeTag *right; // right child
struct NodeTag *parent; // parent
nodeColor color; // node color (BLACK, RED)
KeyType key; // key used for searching
ValType val; // data related to key
} NodeType;
#define SENTINEL &sentinel // all leafs are sentinels
static NodeType sentinel = { SENTINEL, SENTINEL, 0, BLACK, 0};
static NodeType *root = SENTINEL; // root of red-black tree
static void rotateLeft(NodeType *x) {
// rotate node x to left
NodeType *y = x->right;
// establish x->right link
x->right = y->left;
if (y->left != SENTINEL) y->left->parent = x;
// establish y->parent link
if (y != SENTINEL) y->parent = x->parent;
if (x->parent) {
if (x == x->parent->left)
x->parent->left = y;
else
x->parent->right = y;
} else {
root = y;
}
// link x and y
y->left = x;
if (x != SENTINEL) x->parent = y;
}
static void rotateRight(NodeType *x) {
// rotate node x to right
NodeType *y = x->left;
// establish x->left link
x->left = y->right;
if (y->right != SENTINEL) y->right->parent = x;
// establish y->parent link
if (y != SENTINEL) y->parent = x->parent;
if (x->parent) {
if (x == x->parent->right)
x->parent->right = y;
else
x->parent->left = y;
} else {
root = y;
}
// link x and y
y->right = x;
if (x != SENTINEL) x->parent = y;
}
static void insertFixup(NodeType *x) {
// maintain red-black tree balance
// after inserting node x
// check red-black properties
while (x != root && x->parent->color == RED) {
// we have a violation
if (x->parent == x->parent->parent->left) {
NodeType *y = x->parent->parent->right;
if (y->color == RED) {
// uncle is RED
x->parent->color = BLACK;
y->color = BLACK;
x->parent->parent->color = RED;
x = x->parent->parent;
} else {
// uncle is BLACK
if (x == x->parent->right) {
// make x a left child
x = x->parent;
rotateLeft(x);
}
// recolor and rotate
x->parent->color = BLACK;
x->parent->parent->color = RED;
rotateRight(x->parent->parent);
}
} else {
// mirror image of above code
NodeType *y = x->parent->parent->left;
if (y->color == RED) {
// uncle is RED
x->parent->color = BLACK;
y->color = BLACK;
x->parent->parent->color = RED;
x = x->parent->parent;
} else {
// uncle is BLACK
if (x == x->parent->left) {
x = x->parent;
rotateRight(x);
}
x->parent->color = BLACK;
x->parent->parent->color = RED;
rotateLeft(x->parent->parent);
}
}
}
root->color = BLACK;
}
// insert new node (no duplicates allowed)
RbtStatus rbtInsert(KeyType key, ValType val) {
NodeType *current, *parent, *x;
// allocate node for data and insert in tree
// find future parent
current = root;
parent = 0;
while (current != SENTINEL) {
if (compEQ(key, current->key))
return RBT_STATUS_DUPLICATE_KEY;
parent = current;
current = compLT(key, current->key) ?
current->left : current->right;
}
// setup new node
if ((x = malloc (sizeof(*x))) == 0)
return RBT_STATUS_MEM_EXHAUSTED;
x->parent = parent;
x->left = SENTINEL;
x->right = SENTINEL;
x->color = RED;
x->key = key;
x->val = val;
// insert node in tree
if(parent) {
if(compLT(key, parent->key))
parent->left = x;
else
parent->right = x;
} else {
root = x;
}
insertFixup(x);
return RBT_STATUS_OK;
}
static void deleteFixup(NodeType *x) {
// maintain red-black tree balance
// after deleting node x
while (x != root && x->color == BLACK) {
if (x == x->parent->left) {
NodeType *w = x->parent->right;
if (w->color == RED) {
w->color = BLACK;
x->parent->color = RED;
rotateLeft (x->parent);
w = x->parent->right;
}
if (w->left->color == BLACK && w->right->color == BLACK) {
w->color = RED;
x = x->parent;
} else {
if (w->right->color == BLACK) {
w->left->color = BLACK;
w->color = RED;
rotateRight (w);
w = x->parent->right;
}
w->color = x->parent->color;
x->parent->color = BLACK;
w->right->color = BLACK;
rotateLeft (x->parent);
x = root;
}
} else {
NodeType *w = x->parent->left;
if (w->color == RED) {
w->color = BLACK;
x->parent->color = RED;
rotateRight (x->parent);
w = x->parent->left;
}
if (w->right->color == BLACK && w->left->color == BLACK) {
w->color = RED;
x = x->parent;
} else {
if (w->left->color == BLACK) {
w->right->color = BLACK;
w->color = RED;
rotateLeft (w);
w = x->parent->left;
}
w->color = x->parent->color;
x->parent->color = BLACK;
w->left->color = BLACK;
rotateRight (x->parent);
x = root;
}
}
}
x->color = BLACK;
}
// delete node
RbtStatus rbtErase(NodeType * z) {
NodeType *x, *y;
if (z->left == SENTINEL || z->right == SENTINEL) {
// y has a SENTINEL node as a child
y = z;
} else {
// find tree successor with a SENTINEL node as a child
y = z->right;
while (y->left != SENTINEL) y = y->left;
}
// x is y's only child
if (y->left != SENTINEL)
x = y->left;
else
x = y->right;
// remove y from the parent chain
x->parent = y->parent;
if (y->parent)
if (y == y->parent->left)
y->parent->left = x;
else
y->parent->right = x;
else
root = x;
if (y != z) {
z->key = y->key;
z->val = y->val;
}
if (y->color == BLACK)
deleteFixup (x);
free (y);
return RBT_STATUS_OK;
}
// find key
NodeType *rbtFind(KeyType key) {
NodeType *current;
current = root;
while(current != SENTINEL) {
if(compEQ(key, current->key)) {
return current;
} else {
current = compLT (key, current->key) ?
current->left : current->right;
}
}
return NULL;
}
// in-order walk of tree
void rbtInorder(NodeType *p, void (callback)(NodeType *)) {
if (p == SENTINEL) return;
rbtInorder(p->left, callback);
callback(p);
rbtInorder(p->right, callback);
}
// delete nodes depth-first
void rbtDelete(NodeType *p) {
if (p == SENTINEL) return;
rbtDelete(p->left);
rbtDelete(p->right);
free(p);
}
void displayNode(NodeType *p) {
printf("%d %d\n", p->key, p->val.stuff);
}
int main(int argc, char **argv) {
int maxnum, ct;
KeyType key;
RbtStatus status;
// command-line:
//
// rbt 2000
// process 2000 records
NodeType *p;
maxnum = atoi(argv[1]);
printf("maxnum = %d\n", maxnum);
for (ct = maxnum; ct; ct--) {
key = rand() % 90 + 1;
if ((p = rbtFind(key)) != NULL) {
if (p->val.stuff != 10*key) printf("fail val\n");
status = rbtErase(p);
if (status) printf("fail: status = %d\n", status);
} else {
ValType val;
val.stuff = 10*key;
status = rbtInsert(key, val);
if (status) printf("fail: status = %d\n", status);
}
}
// output nodes in order
rbtInorder(root, displayNode);
rbtDelete(root);
return 0;
}
参考:
①Implementation in C: http://epaperpress.com/sortsearch/txt/rbt.txt
②维基百科: http://zh.wikipedia.org/zh/%E7%BA%A2%E9%BB%91%E6%A0%91
③http://epaperpress.com/sortsearch/rbt.html
④saturnma: http://saturnman.blog.163.com/blog/static/5576112010969420383/