1 介绍
这部分终于整理完了,太耗时间了,留下来备忘吧!
之前看STL源码时,只是研究了红黑树的插入部分。在stl源码剖析的书中,也没有涉及到删除操作的分析,这次对删除操作也进行了详细的研究,
并且还是这次学习的重点。下面开始。
红黑树需要遵从下面的5条性质:
(1)节点要么是红色要么是黑色;
(2)根节点为黑色;
(3)叶子节点即NIL节点必定为黑色;
(4)红色节点的孩子节点必定为黑色;
(5)从任一节点到叶子节点,所包含的黑色节点数目相同,即黑高度相同;
上面的5条规则,主要是第(4)、(5)两条保证了红黑树的近似完整平衡性。如下示为一红黑树。
2 红黑树的旋转操作
由于红黑树的插入与删除操作均涉及到红黑树的再平衡,为此这儿先介绍红黑树两平衡过程中涉及到的四种类型的旋转操作。
(1)右旋转
主要处理LL(左左)类型的插入情况,对于LL型的插入进行一次右旋转即可。
(2)左旋转
主要处理RR(右右)类型的插入情况,对于RR型的插入进行一次左旋转即可。
(3)先左旋再右旋
主要处理LR(左右)类型的插入情况,对于LR型的插入需要先已插入节点的父节点进行一次左旋转,再以其父节点进行一次右旋转。
(4)先右旋再左旋
主要处理RL(右左)类型的插入情况,对于RL型的插入需要先已插入节点的父节点进行一次右旋转,再以其父节点进行一次左旋转。
3 红黑树插入
红黑树的插入操作会为以通常的二叉树的插入方式先找到节点正确的插入位置,再将节点插入到树中,最后再对由于新增节点引起的红黑树结点性质的破坏进行恢复。
对于新节点插入红黑树中,树的红黑性质的恢复有以下几种情况:
case 1:红黑树为空,新节点插入作为根结点,如下示:
case 2:新节点的父节点为黑色,此时直接插入后不会破坏红黑树的性质,可以直接插入,如下示:
case 2:父节点为红色,叔父结点也为红色;则需要将父节点及叔父节点均调整为黑色,然后将祖父结点调整为红色,
再以祖父结点为起点,进行红黑性恢复的起点,进行继续调整,如下图示。
case 3:父节点为红色,叔父节点为黑色,父节点为祖父节点的左节点,插入结点在父节点的左节点;
需要以插入节点的左节点进行一次右旋转,此处理类型为LL型。如下示:
case 4:父节点为红色,叔父节点为黑色,父节点为祖父节点的左节点,插入结点在父节点的右节点;
需要先以插入节点的父亲节点为旋转点先进行一次左旋转,再以插入节点的的祖父节点进行一次右旋转,此处理类型即为LR型。如下示:
case 5:父节点为红色,叔父节点为黑色,父节点为祖父节点的右节点,插入结点在父节点的右节点;
需要以插入节点的左节点进行一次左旋转,此处理类型为RR型。如情形3示,操作对称。
case 6:父节点为红色,叔父节点为黑色,父节点为祖父节点的右节点,插入结点在父节点的左节点;
需要先以插入节点的父亲节点为旋转点先进行一次右旋转,再以插入节点的的祖父节点进行一次左旋转,此处理类型即为RL型。如情形4示,操作对称。
下面为nginx的插入源码:
1: void
2: ngx_rbtree_insert(ngx_thread_volatile ngx_rbtree_t *tree,
3: ngx_rbtree_node_t *node)
4: {
5: ngx_rbtree_node_t **root, *temp, *sentinel;
6:
7: /* a binary tree insert */
8: //获取树的根结点
9: root = (ngx_rbtree_node_t **) &tree->root;
10: sentinel = tree->sentinel;
11:
12: if (*root == sentinel) {
13: node->parent = NULL;
14: node->left = sentinel;
15: node->right = sentinel;
16: ngx_rbt_black(node);
17: *root = node;
18:
19: return;
20: }
21: //进行插入操作
22: tree->insert(*root, node, sentinel);
23:
24: /* re-balance tree */
25: //对树进行平衡处理,仅需要处理父结点为红色的情况
26: while (node != *root && ngx_rbt_is_red(node->parent)) {
27: //插入节点的父结点为左结点
28: if (node->parent == node->parent->parent->left) {
29: temp = node->parent->parent->right;
30: //插入结点的叔父结点为红,此时仅需要将叔父结点与父结点改为黑,祖父结点改为红,然后
31: //再以祖父结点为开始,进行平衡
32: if (ngx_rbt_is_red(temp)) {
33: ngx_rbt_black(node->parent);
34: ngx_rbt_black(temp);
35: ngx_rbt_red(node->parent->parent);
36: node = node->parent->parent;
37:
38: } else {
39: //若插入到父结点的右边进行LR旋转
40: if (node == node->parent->right) {
41: node = node->parent;
42: ngx_rbtree_left_rotate(root, sentinel, node);
43: }
44: //若插入到左边进行R旋转
45: ngx_rbt_black(node->parent);
46: ngx_rbt_red(node->parent->parent);
47: ngx_rbtree_right_rotate(root, sentinel, node->parent->parent);
48: }
49:
50: } else {
51: //同上,操作对称
52: temp = node->parent->parent->left;
53:
54: if (ngx_rbt_is_red(temp)) {
55: ngx_rbt_black(node->parent);
56: ngx_rbt_black(temp);
57: ngx_rbt_red(node->parent->parent);
58: node = node->parent->parent;
59:
60: } else {
61: if (node == node->parent->left) {
62: node = node->parent;
63: ngx_rbtree_right_rotate(root, sentinel, node);
64: }
65:
66: ngx_rbt_black(node->parent);
67: ngx_rbt_red(node->parent->parent);
68: ngx_rbtree_left_rotate(root, sentinel, node->parent->parent);
69: }
70: }
71: }
72:
73: ngx_rbt_black(*root);
74: }
4 红黑树删除
实际上红黑树的删除操作,是通过将待删除节点与其后继节点的值进行互换后,通过删除后续节点来完成的;由于后续节点必定是只有一个子结点或者没有子节点的情况,因此有以下几条性质是在红黑树删除时,应知晓的:
(1)删除操作中真正被删除的必定是只有一个红色孩子或没有孩子的节点。
(2)如果真正的删除点是一个红色节点,那么它必定没有孩子节点。
(3)如果真正的删除点是一个黑色节点,那么它要么有一个红色的右孩子,要么没有孩子节点(针对找后继的情况)。
以上的情况主要是由于红黑树性质中的任何节点到叶子节点NIL的路径中,黑色节点的数目一致决定的。针对以上的情形,红黑树的删除操作主要有以下几种情形:
case 1:删除的节点为红色,则它必定无孩子节点。可以直接删除
case 2:删除的节点为黑色且有一个红色的右孩子,这时可以直接用右孩子替换删除节点,将右孩子修改为黑色,红黑性不破坏。
case 3:删除节点为黑色没有孩子节点,那么它有一对NIL节点构成的孩子节点,此时情况较为复杂,有以下几种情况:
case 3.1:待删除结点的兄弟节点为红色,此时其父节点必定为黑色,需要将父节点置为红色,兄弟节点置为黑色,再进行一次左旋转,这样就会变为下面的情况x,继续进行处理。
case 3.2:待删除节点的兄弟节点为黑色,此时需要查看其子侄结点的颜色情况进行处理。
case 3.2.1:子侄节点为全黑色,又需要依据父节点的颜色分情况考虑。
case 3.2.1.1:父节点为红色,需要将父节点变为黑色,兄弟结点变为红色,即可
case 3.2.1.2:父节点为黑色,需要将父节点变为黑色,兄弟结点变为红色,还要将调节指针放于父节点处,继续进行红黑性质恢复处理。
case 3.2.2:子侄节点中右结点为红色,左节点为黑色,需要RR型旋转。
case 3.2.3 子侄节点中左结点为红色,右节点为黑色,需要RL型旋转。
剩下的为一些对称情况,有时间再讨论。
下面看nginx的源码:
1: void2: ngx_rbtree_delete(ngx_thread_volatile ngx_rbtree_t *tree,
3: ngx_rbtree_node_t *node)
4: {
5: ngx_uint_t red;
6: ngx_rbtree_node_t **root, *sentinel, *subst, *temp, *w;
7:
8: /* a binary tree delete */9: //取得树的根结点及NIL结点10: root = (ngx_rbtree_node_t **) &tree->root;
11: sentinel = tree->sentinel;
12: /* 下面是获取node结点的后继结点,temp指向替换结点,subst指向后继结点,即待删除结点13: * 当待删除结点有左右子树为空时,temp指向非空子结点,subst指向node结点14: * 否则,temp指向替换结点,subst指向后继结点15: */16: if (node->left == sentinel) {17: temp = node->right;
18: subst = node;
19:
20: } else if (node->right == sentinel) {21: temp = node->left;
22: subst = node;
23:
24: } else {25: //查找后继结点26: subst = ngx_rbtree_min(node->right, sentinel);
27: //得到替换结点的指针28: if (subst->left != sentinel) {29: temp = subst->left;
30: } else {31: temp = subst->right;
32: }
33: }
34: //如果待删除结点为根结点,些时的情况是要么树中就一个根结点,要么存在一个红色的孩子35: //调整树的根结点指针,更改替换结点的颜色36: if (subst == *root) {37: *root = temp;
38: ngx_rbt_black(temp);
39:
40: /* DEBUG stuff */41: node->left = NULL;
42: node->right = NULL;
43: node->parent = NULL;
44: node->key = 0;
45:
46: return;47: }
48: //保存后继结点的红黑性49: red = ngx_rbt_is_red(subst);
50: //先直接删除后继结点,将替换结点连接至后继结点的父结点51: //先将subst从树中删除出来52: if (subst == subst->parent->left) {53: subst->parent->left = temp;
54:
55: } else {56: subst->parent->right = temp;
57: }
58: //然后是根据node的情况处理59: //首先当node与subst相同,即node存在左子树或者右子树为空时的情况时,直接删除,连接子结60: //点的parent指针到node的父结点61: if (subst == node) {62:
63: temp->parent = subst->parent;
64: //若不同,则直接用subst结点替换node结点,而不是进行值的复制65: } else {66: //修改替换结点的父指针67: if (subst->parent == node) {68: temp->parent = subst;
69:
70: } else {71: temp->parent = subst->parent;
72: }
73: //完成后继结点替换node结点,下面是复制node结点的指针值及红黑性74: subst->left = node->left;
75: subst->right = node->right;
76: subst->parent = node->parent;
77: ngx_rbt_copy_color(subst, node);
78: //若node为根结点,修改树的根结点指针79: if (node == *root) {80: *root = subst;
81:
82: } else {83: //或者修改node的父结点对subst的指向84: if (node == node->parent->left) {85: node->parent->left = subst;
86: } else {87: node->parent->right = subst;
88: }
89: }
90: //修改以前的node结点的子结点的指向91: if (subst->left != sentinel) {92: subst->left->parent = subst;
93: }
94:
95: if (subst->right != sentinel) {96: subst->right->parent = subst;
97: }
98: }
99:
100: /* DEBUG stuff */101: node->left = NULL;
102: node->right = NULL;
103: node->parent = NULL;
104: node->key = 0;
105: //case 1:后继结点为红色,则必定满足红黑树的性质不需要做调整106: if (red) {107: return;108: }
109:
110: /* a delete fixup */111: //case 2.1:后继结点为黑色,且替换结点为黑色,实际上此时的替换结点为NIL,112: //但因为删除了一个黑色结点导致黑高度减一,红黑树性质破坏,调整红黑树113: while (temp != *root && ngx_rbt_is_black(temp)) {114: //case 2.1.1:替换结点为左结点115: if (temp == temp->parent->left) {116: //w指向右兄弟结点117: w = temp->parent->right;
118: //case 2.1.1.1:右兄弟结点为红色,此时将右兄弟结点变黑,父结点变红(父结点以前的颜色不影响最后结果)119: //再进行一次左旋转,实际上将些种情形转变为下面的三种情况。120: if (ngx_rbt_is_red(w)) {121: ngx_rbt_black(w);
122: ngx_rbt_red(temp->parent);
123: ngx_rbtree_left_rotate(root, sentinel, temp->parent);
124: w = temp->parent->right;
125: }
126: //case 2.1.1.2:右兄弟结点拥有两,这种情况要分两种处理127: //case 2.1.1.2.1:父结点为黑色,则将右兄弟结点变红后,还需要将指针移到父结点,进行继续处理128: //case 2.1.1.2.2:父结点为红色,则将右兄弟结点变红后,将父结点变黑,处理就ok了,不过这个父结点129: //变黑是在最后进行处理的,以防现在变黑继续处理。130: //还需要注意一个问题:这里的右兄弟结点必定为黑色的,为红色情况已经优先处理了。131: if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {132: ngx_rbt_red(w);
133: temp = temp->parent;
134: //case 2.1.1.3:右兄弟结点的了结点颜色不一致,些时需要分情况讨论135: } else {136: //case 2.1.1.3.1:右子侄结点为黑色,进行RL旋转137: if (ngx_rbt_is_black(w->right)) {138: ngx_rbt_black(w->left);
139: ngx_rbt_red(w);
140: ngx_rbtree_right_rotate(root, sentinel, w);
141: w = temp->parent->right;
142: }
143: //case 2.1.1.3.2:右子侄结点为红色,进行L旋转144: ngx_rbt_copy_color(w, temp->parent);
145: ngx_rbt_black(temp->parent);
146: ngx_rbt_black(w->right);
147: ngx_rbtree_left_rotate(root, sentinel, temp->parent);
148: temp = *root;
149: }
150:
151: } else {152: //case 2.1.2:替换结点为右结点,原理同上,操作对称,不详解(话说这情况时是不是左子树为空啊!)153: w = temp->parent->left;
154:
155: if (ngx_rbt_is_red(w)) {156: ngx_rbt_black(w);
157: ngx_rbt_red(temp->parent);
158: ngx_rbtree_right_rotate(root, sentinel, temp->parent);
159: w = temp->parent->left;
160: }
161:
162: if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {163: ngx_rbt_red(w);
164: temp = temp->parent;
165:
166: } else {167: if (ngx_rbt_is_black(w->left)) {168: ngx_rbt_black(w->right);
169: ngx_rbt_red(w);
170: ngx_rbtree_left_rotate(root, sentinel, w);
171: w = temp->parent->left;
172: }
173:
174: ngx_rbt_copy_color(w, temp->parent);
175: ngx_rbt_black(temp->parent);
176: ngx_rbt_black(w->left);
177: ngx_rbtree_right_rotate(root, sentinel, temp->parent);
178: temp = *root;
179: }
180: }
181: }
182: //case 2.2 后继结点为黑色,替换结点为红色,直接修改替换结点为黑色,183: //红黑树的性质得到恢复184: ngx_rbt_black(temp);
185: }
以上部分图片引用自
http://blog.sina.com.cn/s/blog_4d6f8e050101kqmw.html;
http://blog.csdn.net/gabriel1026/article/details/6311339;
其余均为原创转载请注明出处:http://www.cnblogs.com/doop-ymc/p/3440316.html