nginx 紅黑樹詳解

1 介紹

這部分終於整理完了,太耗時間了,留下來備忘吧!html

以前看STL源碼時,只是研究了紅黑樹的插入部分。在stl源碼剖析的書中,也沒有涉及到刪除操做的分析,此次對刪除操做也進行了詳細的研究,node

而且仍是此次學習的重點。下面開始。nginx

紅黑樹須要聽從下面的5條性質:app

(1)節點要麼是紅色要麼是黑色;oop

(2)根節點爲黑色;學習

(3)葉子節點即NIL節點一定爲黑色;ui

(4)紅色節點的孩子節點一定爲黑色;spa

(5)從任一節點到葉子節點,所包含的黑色節點數目相同,即黑高度相同;.net

上面的5條規則,主要是第(4)、(5)兩條保證了紅黑樹的近似完整平衡性。以下示爲一紅黑樹。3d

wer

2 紅黑樹的旋轉操做

因爲紅黑樹的插入與刪除操做均涉及到紅黑樹的再平衡,爲此這兒先介紹紅黑樹兩平衡過程當中涉及到的四種類型的旋轉操做。

(1)右旋轉

主要處理LL(左左)類型的插入狀況,對於LL型的插入進行一次右旋轉便可。

106689_1302084936IP2I

(2)左旋轉

主要處理RR(右右)類型的插入狀況,對於RR型的插入進行一次左旋轉便可。

106689_1302084939o44u

(3)先左旋再右旋

主要處理LR(左右)類型的插入狀況,對於LR型的插入須要先已插入節點的父節點進行一次左旋轉,再以其父節點進行一次右旋轉。

106689_13020849378y93

(4)先右旋再左旋

主要處理RL(右左)類型的插入狀況,對於RL型的插入須要先已插入節點的父節點進行一次右旋轉,再以其父節點進行一次左旋轉。

106689_130208493865I7

3 紅黑樹插入

紅黑樹的插入操做會爲以一般的二叉樹的插入方式先找到節點正確的插入位置,再將節點插入到樹中,最後再對因爲新增節點引發的紅黑樹結點性質的破壞進行恢復。

對於新節點插入紅黑樹中,樹的紅黑性質的恢復有如下幾種狀況:

case 1:紅黑樹爲空,新節點插入做爲根結點,以下示:

1

case 2:新節點的父節點爲黑色,此時直接插入後不會破壞紅黑樹的性質,能夠直接插入,以下示:

2

case 2:父節點爲紅色,叔父結點也爲紅色;則須要將父節點及叔父節點均調整爲黑色,而後將祖父結點調整爲紅色,

再以祖父結點爲起點,進行紅黑性恢復的起點,進行繼續調整,以下圖示。

Red-black_tree_insert_case_3

case 3:父節點爲紅色,叔父節點爲黑色,父節點爲祖父節點的左節點,插入結點在父節點的左節點;

須要以插入節點的左節點進行一次右旋轉,此處理類型爲LL型。以下示:

Red-black_tree_insert_case_5

case 4:父節點爲紅色,叔父節點爲黑色,父節點爲祖父節點的左節點,插入結點在父節點的右節點;

須要先以插入節點的父親節點爲旋轉點先進行一次左旋轉,再以插入節點的的祖父節點進行一次右旋轉,此處理類型即爲LR型。以下示:

Red-black_tree_insert_case_4飛信截圖20131124193717

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:刪除的節點爲紅色,則它一定無孩子節點。能夠直接刪除

image

case 2:刪除的節點爲黑色且有一個紅色的右孩子,這時能夠直接用右孩子替換刪除節點,將右孩子修改成黑色,紅黑性不破壞。

image

case 3:刪除節點爲黑色沒有孩子節點,那麼它有一對NIL節點構成的孩子節點,此時狀況較爲複雜,有如下幾種狀況:

case 3.1:待刪除結點的兄弟節點爲紅色,此時其父節點一定爲黑色,須要將父節點置爲紅色,兄弟節點置爲黑色,再進行一次左旋轉,這樣就會變爲下面的狀況x,繼續進行處理。

image

case 3.2:待刪除節點的兄弟節點爲黑色,此時須要查看其子侄結點的顏色狀況進行處理。

        case 3.2.1:子侄節點爲全黑色,又須要依據父節點的顏色分狀況考慮。

                 case 3.2.1.1:父節點爲紅色,須要將父節點變爲黑色,兄弟結點變爲紅色,便可

                   image

                  case 3.2.1.2:父節點爲黑色,須要將父節點變爲黑色,兄弟結點變爲紅色,還要將調節指針放於父節點處,繼續進行紅黑性質恢復處理。

                  image

         case 3.2.2:子侄節點中右結點爲紅色,左節點爲黑色,須要RR型旋轉。

        image

        case 3.2.3 子侄節點中左結點爲紅色,右節點爲黑色,須要RL型旋轉。

        image

剩下的爲一些對稱狀況,有時間再討論。

下面看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

相關文章
相關標籤/搜索