由于看个东西,发现要用到红黑树,所以拿算法导论看了下红黑树的定义和实现,第一遍看发现红黑树挺复杂的,第二遍再看发现好了点,第三遍又好点。。。n遍之后终于有点理解了。最后打算自己实现这个红黑树,也能更好的理解它。
1 红黑色定义
首先红黑树的定义是非常重要的,它的定义如下:
1.一个结点要么是黑色,要么是红色,只能是其中的一种。
2.树的根结点一定是黑色。
3.如果一个结点为红色,那么它的孩子结点必定为黑色。
4.从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
正是有了这个定义,使得红黑树保持了良好的性质,如树的高度不会超过2lgn,所以对某个关键字的查找时间为o(lgn),并且向树中插入和删除结点的维护代价也是o(lgn),并且比AVL树的要小,这点在后面我们可以看到。
2 树的插入
树的插入可以分两步走:
1.找到插入节点要插入的位置,并将节点插入到树中。
2.从插入的节点开始调整树,使树保持定义。
我们先来看插入操作:红黑树的插入操作其实和二叉排序树是一样的,将新插入节点的key与树根的key比较,如果小于,与树根的左孩子比较,否则与树的有孩子比较,直到叶结点为止。然后与叶结点比较判断该节点应该插入该叶节点的左边还是右边就ok了,当然不要忘记把新插入的结点着为红色。代码如下,如果有bug,望指正哈。
1 void rb_insert_node(rb_node_t *new, rb_node_t **root) 2 { 3 rb_node_t *parent = NULL, *temp = *root; 4 5 while (temp) 6 { 7 if (key_compare(new->key, temp->key) < 0) 8 { 9 /*新插入的节点key值比当前节点key值小*/ 10 parent = temp;11 temp = temp->lchild;12 }13 else14 {15 /*新插入的节点key值大于或等于当前节点key的值*/ 16 parent = temp;17 temp = temp->rchild;18 }19 }20 new->lchild = new->rchild = NULL;21 if (NULL == *root)22 {23 new->color = BLACK;24 new->parent = NULL;25 *root = new;26 }27 else28 {29 if (key_compare(new->key, parent->key) < 0) 30 parent->lchild = new;31 else32 parent->rchild = new;33 new->color = RED;34 new->parent = parent;35 /*如果父节点为红色就需要调整*/36 if (RED == parent->color)37 rb_insert_fix_node(new, root);38 }39 }
然后就是调整结点来保存红黑色的性质了。如果插入结点的父结点是黑色,那没有必要去调整,因为新插入的结点为红色,并不会违法红黑树的定义。那么需要调整是新插入结点的父结点为红色的情况,因为它会违反定义的第3点。
插入的调整包括6种情况,但实际上是3种,因为另外3种完全是对称的。所以在这里我就只分析3种,也就是当父结点是其父结点的左孩子的情况(有点绕口哈)。现在我们可以想想,新插入的节点会违反红黑树定义的那些呢?第1点肯定不会;第2点也不会(除非开始树是空的);第4点也不会,因为插入的是红色结点,对黑色结点的统计根本没有影响;真正会违反的是第3点,由于插入结点的父结点也是红色。那么我们应该怎样调整呢?首先我们把插入结点作为调整结点,这时就有三种情况了,分别是:(1)父亲的兄弟结点为红色;(2)父亲的兄弟结点为黑色,调整的结点为父结点的右孩子;(3)父亲的兄弟结点为黑色,调整的结点为父结点的左孩子。情况(1)可以通过将父亲结点和父亲的兄弟结点调整为黑色,父结点的父结点调整为红色,把父结点的父节点作为新的调整结点;情况(2)可以通过左旋父结点,并将父结点作为新的调整结点,这就转化为了情况(3);情况(3)通过设置父结点为黑色,父结点的父结点设置为红色,右旋父结点的父结点来解决。这样调整最多是两次旋转,代价为o(lgn),代码如下:
1 void rb_insert_fix_node(rb_node_t *node, rb_node_t **root) 2 { 3 rb_node_t *s, *p = node->parent, *ps, *pp; 4 while (p && p != *root && p->color == RED) 5 { 6 pp = p->parent; 7 if (p == pp->lchild) 8 { 9 /*p为pp的左孩子*/ 10 ps = pp->rchild; 11 if (ps && p->color == ps->color) 12 { 13 /*情况1:父节点和父节点的兄弟均为红色 14 * PP pp 15 * / \ / \ 16 * p ps --> P PS 17 * / / 18 *n n 19 ************************************/ 20 p->color = ps->color = BLACK; 21 pp->color = RED; 22 node = pp; 23 p = node->parent; 24 } 25 else 26 { 27 if (node == p->rchild) 28 { 29 /*情况2:父节点的兄弟为黑色,node为父节点的右孩子,左旋p,将p设为n 30 * PP pp 31 * / \ / \ 32 * p PS --> n PS 33 * \ / 34 * n p 35 **********************************************/ 36 rb_rotate_left(p, node); 37 node = p; 38 p = p->parent; 39 } 40 else 41 { 42 /*情况3:父节点的兄弟为黑色,node为父节点的左孩子,将p置为黑色,pp置为红色,右旋pp 43 * PP P 44 * / \ / \ 45 * p PS --> n pp 46 * / \ 47 *n PS 48 **********************************************/ 49 p->color = BLACK; 50 pp->color = RED; 51 rb_rotate_right(pp, p); 52 if (NULL == p->parent) 53 *root = p; 54 p = *root; 55 } 56 } 57 } 58 else 59 { 60 /*p为pp的右孩子*/ 61 ps = pp->lchild; 62 if (ps && p->color == ps->color) 63 { 64 /*情况1:父节点和父节点的兄弟均为红色 65 * PP pp 66 * / \ / \ 67 * ps p --> PS P 68 * \ \ 69 * n n 70 ************************************/ 71 p->color = ps->color = BLACK; 72 pp->color = RED; 73 node = pp; 74 p = node->parent; 75 } 76 else 77 { 78 if (node == p->lchild) 79 { 80 /*情况2:父节点的兄弟为黑色,node为父节点的左孩子,右旋p,将p设为n 81 * PP pp 82 * / \ / \ 83 * PS P --> PS n 84 * / \ 85 * n p 86 **********************************************/ 87 rb_rotate_right(p, node); 88 node = p; 89 p = p->parent; 90 } 91 else 92 { 93 /*情况3:父节点的兄弟为黑色,node为父节点的右孩子,将p置为黑色,pp置为红色,左旋pp 94 * PP P 95 * / \ / \ 96 * PS p --> pp n 97 * \ / 98 * n PS 99 **********************************************/100 p->color = BLACK;101 pp->color = RED;102 rb_rotate_left(pp, p);103 /*根节点改变了*/104 if (NULL == p->parent)105 *root = p;106 p = *root;107 }108 }109 }110 }111 (*root)->color = BLACK;112 }