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: void

   2: 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://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91#.E6.B8.90.E8.BF.9B.E8.BE.B9.E7.95.8C.E7.9A.84.E8.AF.81.E6.98.8E

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

nginx 红黑树详解的更多相关文章

  1. DS 红黑树详解

    通过上篇博客知道,二叉搜索树的局限在于不能完成自平衡,从而导致不能一直保持高性能. AVL树则定义了平衡因子绝对值不能大于1,使二叉搜索树达到了严格的高度平衡. 还有一种能自我调整的二叉搜索树, 红黑 ...

  2. Nginx配置文件nginx.conf中文详解(转)

    ######Nginx配置文件nginx.conf中文详解##### #定义Nginx运行的用户和用户组 user www www; #nginx进程数,建议设置为等于CPU总核心数. worker_ ...

  3. Nginx配置文件nginx.conf中文详解【转】

    PS:Nginx使用有两三年了,现在经常碰到有新用户问一些很基本的问题,我也没时间一一回答,今天下午花了点时间,结合自己的使用经验,把Nginx的主要配置参数说明分享一下,也参考了一些网络的内容,这篇 ...

  4. Nginx配置文件中文详解

    ######Nginx配置文件nginx.conf中文详解##### #定义Nginx运行的用户和用户组 user www www; #nginx进程数,建议设置为等于CPU总核心数. worker_ ...

  5. nginx.conf配置详解

    ######Nginx配置文件nginx.conf中文详解##### #定义Nginx运行的用户和用户组 user www www; #nginx进程数,建议设置为等于CPU总核心数. worker_ ...

  6. Nginx安全相关配置和nginx.conf中文详解

    一.centos下redis安全相关 1.背景 在使用云服务器时,如果我们的redis关闭了protected-mode模式,被病毒攻击的可能会大大增加,因此我们使用redis时候,最好更改默认端口, ...

  7. Nginx 安装与详解

    nginx简介 nginx是一个开源的,支持高性能,高并发的www服务和代理服务软件.它是一个俄罗斯人lgor sysoev开发的,作者将源代码开源出来供全球使用.nginx比它大哥apache性能改 ...

  8. Nginx配置项优化详解【转】

    (1)nginx运行工作进程个数,一般设置cpu的核心或者核心数x2 如果不了解cpu的核数,可以top命令之后按1看出来,也可以查看/proc/cpuinfo文件 grep ^processor / ...

  9. Nginx的配置详解

    人无再少年,花有重开日——风城玫瑰 德里克·罗斯 Nginx是一款轻量级的HTTP服务器,采用事件驱动的异步非阻塞处理方式框架,这让其具有极好的IO性能,时常用于服务端的反向代理和负载均衡. Ngin ...

随机推荐

  1. C陷阱与缺陷 第一章

    1. 使用 e1=e2的赋值方式 作为 条件语句内部的判断,请使用显示的判断 不使用: if( x =y ) foo(); 而使用: ) foo(); 2. 注意编码规范,一定要在赋值号 “=”两边, ...

  2. win8上cmder文字重叠问题

    1.用过ubuntu上的bash,zsh后发现win自带的cmd弱爆了,在网上搜索后找到了代替品cmder,下载安装后好发现中文错位的问题, 状况如下: 修复方法:把设置里面的Monospace选项勾 ...

  3. Spring MVC中,事务是否可以加在Controller层

    一般而言,事务都是加在Service层的,但是爱钻牛角尖的我时常想:事务加在Controller层可不可以.我一直试图证明事务不止可以加在Service层,还可以加在Controller层,但是没有找 ...

  4. PARTITION BY 和 group by

    sum()   over   (PARTITION   BY   ...)   是一个分析函数.   他执行的效果跟普通的sum   ...group   by   ...不一样,它计算组中表达式的累 ...

  5. mplayer最全的命令

    前段时间做过qt内嵌mplayer的一个小程序,感觉mplayer还行不过不支持打开图片感觉有点无力.话不多说上代码: QString path="d:/1.mkv"; QWidg ...

  6. HDU 1814 Peaceful Commission

    2-SAT,输出字典序最小的解,白书模板. //TwoSAT输出字典序最小的解的模板 //注意:0,1是一组,1,2是一组..... #include<cstdio> #include&l ...

  7. PopupWindow组件的使用问题

    //如果默认屏幕的话,父view就写自己 popupWindow.showAtLocation(inflate, Gravity.BOTTOM, 0, 0); PopupWindow 顾名思义为弹出式 ...

  8. ignite学习笔记

    1.一个Ignite节点可以从命令行启动,可以用默认的配置也可以传递一个配置文件.可以启动很多很多的节点然后他们会自动地发现对方. 2.Ignite只需要一个ignite-core强依赖,通常你还需要 ...

  9. 获取map中的一个value值以及遍历map获得map里所有key、value的值

    前言: 1.声明一个map: Map map = new HashMap();2.向map中放值,注意:map是key-value的形式存放的.如: map.put(”sa”,”dd”); 3.从ma ...

  10. cell选中与取消选中调用的方法

    //选中与取消选中都会调用哦,注意!!- (void)setSelected:(BOOL)selected animated:(BOOL)animated{ [super setSelected:se ...