1. 程式人生 > >學習演算法導論——紅黑樹旋轉插入和刪除

學習演算法導論——紅黑樹旋轉插入和刪除

參考:

《演算法導論》

紅黑樹是一棵二叉搜尋樹,每個節點有一個標誌位表示顏色,該顏色可以是紅(RED)或黑(BLACK)。通過對任何一條從根到葉子的簡單路徑上各點的顏色進行約束,就能確保沒有一條路徑會比其他路徑長出2倍,因而是近似於平衡的。

紅黑樹每個節點有5個屬性,color,key,leftChild,rightChild,p。其中color是顏色,key是關鍵字,leftChild是指向左孩子的指標,rightChild是指向右孩子的指標,p是指向父節點的指標。如果一個節點沒有子節點或父節點,則該節點的leftChild,rightChild,p指標指向NIL。這些NIL視為二叉搜尋樹的外部節點,其他節點為內部節點。

一棵紅黑樹有如下性質:

(1)每個節點或是紅色,或是黑色;

(2)根節點是黑色的;

(3)每個葉節點(NIL節點)是黑色的;

(4)如果一個節點是紅色的,則它的兩個子節點都是黑色的;

(5)對每個節點,從該節點到其所有後代葉節點的簡單路徑上,均包含相同數目的黑色節點。

如圖(a)為一棵紅黑樹;


我們可以把NIL看成哨兵T.nil,它的color是黑色,其他屬性任意。如圖(b);


左旋

指標結構的修改是通過旋轉來完成的,這時一種能保持二叉搜尋樹性質的操作。旋轉有左旋和右旋兩個,左旋操作為:


如圖,對x作左旋操作。假設x的右孩子為y(不是NIL),x可以是任何右孩子不為NIL的內部節點,簡單理解為:

以x為中心向左旋轉,交換x,y;α,β,γ順序不變。其中還要注意,每個節點還有指向父節點指標。

動圖:


左旋的虛擬碼:

LEFT-ROTATE(T, x)  
01  y ← right[x]            // 前提:這裡假設x的右孩子為y。下面開始正式操作
02  right[x] ← left[y]      // 將 “y的左孩子” 設為 “x的右孩子”,即 將β設為x的右孩子
03  p[left[y]] ← x          // 將 “x” 設為 “y的左孩子的父親”,即 將β的父親設為x
04  p[y] ← p[x]             // 將 “x的父親” 設為 “y的父親”
05  if p[x] = nil[T]       
06  then root[T] ← y                 // 情況1:如果 “x的父親” 是空節點,則將y設為根節點
07  else if x = left[p[x]]  
08            then left[p[x]] ← y    // 情況2:如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子”
09            else right[p[x]] ← y   // 情況3:(x是它父節點的右孩子) 將y設為“x的父節點的右孩子”
10  left[y] ← x             // 將 “x” 設為 “y的左孩子”
11  p[x] ← y                // 將 “x的父節點” 設為 “y”

c++程式碼:
void Left_Rotate(BiTreeNode* root,BiTreeNode* x)
{
	BiTreeNode* y = x->rightChild;
	x->rightChild = y->leftChild;
	y->leftChild->p = x;

	y->p = x->p;

	if (x->p == NULL)
		root = y;
	else if (x->p->leftChild == x)
		x->p->leftChild = y;
	else
		x->p->rightChild = y;

	y->leftChild= x;
	x->p = y;
}


如上圖,對節點“11”左旋;要注意的是:如果x是根節點,旋轉後y稱為根節點;還有一點,需要知道x是其父節點的左孩子還是右孩子。

右旋

右旋和左旋基本相同:



虛擬碼:

RIGHT-ROTATE(T, y)  
01  x ← left[y]             // 前提:這裡假設y的左孩子為x。下面開始正式操作
02  left[y] ← right[x]      // 將 “x的右孩子” 設為 “y的左孩子”,即 將β設為y的左孩子
03  p[right[x]] ← y         // 將 “y” 設為 “x的右孩子的父親”,即 將β的父親設為y
04  p[x] ← p[y]             // 將 “y的父親” 設為 “x的父親”
05  if p[y] = nil[T]       
06  then root[T] ← x                 // 情況1:如果 “y的父親” 是空節點,則將x設為根節點
07  else if y = right[p[y]]  
08            then right[p[y]] ← x   // 情況2:如果 y是它父節點的右孩子,則將x設為“y的父節點的左孩子”
09            else left[p[y]] ← x    // 情況3:(y是它父節點的左孩子) 將x設為“y的父節點的左孩子”
10  right[x] ← y            // 將 “y” 設為 “x的右孩子”
11  p[y] ← x                // 將 “y的父節點” 設為 “x”

c++程式碼:

void Right_Rotate(BiTreeNode* root, BiTreeNode* y)
{
	BiTreeNode* x = y->leftChild;

	y->leftChild = x->rightChild;
	x->rightChild->p = y;

	x->p = y->p;

	if (y->p == NULL)
		root = x;
	else if (y->p->rightChild == y)
		y->p->rightChild = x;
	else
		y->p->leftChild = x;

	x->rightChild = y;
	y->p = x;
}

插入

我們先當紅黑樹為普通的二叉搜尋樹一樣插入節點z,並將其顏色置為RED,然後,為了保證紅黑樹性質,呼叫一個輔助函式來調整紅黑樹。 虛擬碼:
RB-INSERT(T, z)  
01  y ← nil[T]                        // 新建節點“y”,將y設為空節點。
02  x ← root[T]                       // 設“紅黑樹T”的根節點為“x”
03  while x ≠ nil[T]                  // 找出要插入的節點“z”在二叉樹T中的位置“y”
04      do y ← x                      
05         if key[z] < key[x]  
06            then x ← left[x]  
07         else x ← right[x]  
08  p[z] ← y                          // 設定 “z的父親” 為 “y”
09  if y = nil[T]                     
10     then root[T] ← z               // 情況1:若y是空節點,則將z設為根
11   else if key[z] < key[y]        
12      then left[y] ← z       // 情況2:若“z所包含的值” < “y所包含的值”,則將z設為“y的左孩子”
13   else right[y] ← z      // 情況3:(“z所包含的值” >= “y所包含的值”)將z設為“y的右孩子” 
14  left[z] ← nil[T]                  // z的左孩子設為空
15  right[z] ← nil[T]                 // z的右孩子設為空。至此,已經完成將“節點z插入到二叉樹”中了。
16  color[z] ← RED                    // 將z著色為“紅色”
17  RB-INSERT-FIXUP(T, z)             // 通過RB-INSERT-FIXUP對紅黑樹的節點進行顏色修改以及旋轉,讓樹T仍然是一顆紅黑樹

主要是插入節點後紅黑樹性質的調整函式

虛擬碼:

RB - INSERT - FIXUP(T, z)
01 while color[p[z]] = RED                          // 若“當前節點(z)的父節點是紅色”,則進行以下處理。
02     if p[z] = left[p[p[z]]]                      // 若“z的父節點”是“z的祖父節點的左孩子”,則進行以下處理。
03           y ← right[p[p[z]]]                    // 將y設定為“z的叔叔節點(z的祖父節點的右孩子)”
04           if color[y] = RED                      // Case 1條件:叔叔是紅色
05                 color[p[z]] ← BLACK ? Case 1   //  (01) 將“父節點”設為黑色。
06                 color[y] ← BLACK ? Case 1      // (02) 將“叔叔節點”設為黑色。
07                 color[p[p[z]]] ← RED ? Case 1   //  (03) 將“祖父節點”設為“紅色”。
08                  z ← p[p[z]] ? Case 1   //  (04) 將“祖父節點”設為“當前節點”(紅色節點)
09             else if z = right[p[z]]      // Case 2條件:叔叔是黑色,且當前節點是右孩子
10                  z ← p[z] ? Case 2   //  (01) 將“父節點”作為“新的當前節點”。
11                  LEFT - ROTATE(T, z) ? Case 2   //  (02) 以“新的當前節點”為支點進行左旋。
12               color[p[z]] ← BLACK ? Case 3   // Case 3條件:叔叔是黑色,且當前節點是左孩子。(01) 將“父節點”設為“黑色”。
13		 color[p[p[z]]] ← RED ? Case 3   //  (02) 將“祖父節點”設為“紅色”。
14		 RIGHT - ROTATE(T, p[p[z]]) ? Case 3   //  (03) 以“祖父節點”為支點進行右旋。
15       else (same as then clause with "right" and "left" exchanged)      // 若“z的父節點”是“z的祖父節點的右孩子”,將上面的操作中“right”和“left”交換位置,然後依次執行。
16 color[root[T]] ← BLACK

void RB_Insert_FixUp(BiTreeNode* root, BiTreeNode* z)
{
	BiTreeNode* y;
	while (z->p && z->p->Color == RED)
	{
		if (z->p == z->p->p->leftChild)
		{
			y = z->p->p->rightChild;
			if (y->Color == RED)
			{
				z->p->Color = BLACK;
				y->Color = BLACK;
				z->p->p->Color = RED;
				z = z->p->p;
			}
			else 
			{
				if (z == z->p->rightChild)
				{
					z = z->p;
					Left_Rotate(root, z);
				}
				z->p->Color = BLACK;
				z->p->p->Color = RED;
				Right_Rotate(root, z->p->p);
			}
		}
		else
		{
			y = z->p->p->leftChild;
			if (y->Color == RED)
			{
				z->p->Color = BLACK;
				y->Color = BLACK;
				z->p->p->Color = RED;
				z = z->p->p;
			}
			else 
			{
				if (z == z->p->leftChild)
				{
					z = z->p;
					Right_Rotate(root, z);
				}
				z->p->Color = BLACK;
				z->p->p->Color = RED;
				Left_Rotate(root, z->p->p);
			}
		}
	}
}


刪除

將紅黑樹T內的節點z刪除。需要執行的操作依次是:首先,將T當作一顆二叉樹,將節點刪除;然後,通過RB - DELETE - FIXUP來對節點重新著色並旋轉,以此來保證刪除節點後的樹仍然是一顆紅黑樹。


(01) 將T當作一顆二叉樹,將節點刪除。


這和"刪除常規二叉搜尋樹中刪除節點的方法是一樣的"。分3種情況:
第一種,被刪除節點沒有兒子,即為葉節點。那麼,直接將該節點刪除就OK了。
第二種,被刪除節點只有一個兒子。那麼,直接刪除該節點,並用該節點的唯一子節點頂替它的位置。
第三種,被刪除節點有兩個兒子。那麼,首先把“它的後繼節點的內容”複製給“該節點的內容”;之後,刪除“它的後繼節點”。


這裡有兩點需要說明:第一步中複製時,僅僅複製內容,即將“它的後繼節點的內容”複製給“該節點的內容”。    這相當於用“該節點的後繼節點”取代“該節點”,之後就刪除“該節點的後繼節點”即可,而不需要刪除“該節點”(因為“該節點”已經被“它的後繼節點”所取代)。
第二步中刪除“該節點的後繼節點”時,需要注意:“該節點的後繼節點”不可能是雙子非空,這個根據二叉樹的特性可知。 既然“該節點的後繼節點”不可能雙子都非空,就意味著“該節點的後繼節點”要麼沒有兒子,要麼只有一個兒子。若沒有兒子,則按“第一種”種的辦法進行處理;若只有一個兒子,則按“第二種”中的辦法進行處理。


(02) 通過RB - DELETE - FIXUP來對節點重新著色並旋轉,以此來保證刪除節點後的樹仍然是一顆紅黑樹。


因為(01)中刪除節點之後,可能會違背紅黑樹的特性。所以需要,通過RB - DELETE - FIXUP來重新校正,為當前樹保持紅黑樹的特性。




下面是《演算法導論》中 “從紅黑樹T中刪除節點z”的虛擬碼

RB - DELETE(T, z)
01 if left[z] = nil[T] or right[z] = nil[T]
02    then y ← z                                  // 若“z的左孩子” 或 “z的右孩子”為空,則將“z”賦值給 “y”;
03    else y ← TREE - SUCCESSOR(z)                  // 否則,將“z的後繼節點”賦值給 “y”。
04 if left[y] ≠ nil[T]
05    then x ← left[y]                            // 若“y的左孩子” 不為空,則將“y的左孩子” 賦值給 “x”;
06    else x ← right[y]                           // 否則,“y的右孩子” 賦值給 “x”。
07 p[x] ← p[y]                                    // 將“y的父節點” 設定為 “x的父節點”
08 if p[y] = nil[T]
09    then root[T] ← x                            // 情況1:若“y的父節點” 為空,則設定“x” 為 “根節點”。
10    else if y = left[p[y]]
11            then left[p[y]] ← x                 // 情況2:若“y是它父節點的左孩子”,則設定“x” 為 “y的父節點的左孩子”
12            else right[p[y]] ← x                // 情況3:若“y是它父節點的右孩子”,則設定“x” 為 “y的父節點的右孩子”
13 if y ≠ z
14    then key[z] ← key[y]                        // 若“y的值” 賦值給 “z”。注意:這裡只拷貝z的值給y,而沒有拷貝z的顏色!!!
15         copy y's satellite data into z         
16 if color[y] = BLACK
17    then RB - DELETE - FIXUP(T, x)                  // 若“y為黑節點”,則呼叫
18 return y

RB - DELETE - FIXUP(T, x)
01 while x ≠ root[T] and color[x] = BLACK
02     do if x = left[p[x]]
03           then w ← right[p[x]]                                             // 若 “x”是“它父節點的左孩子”,則設定 “w”為“x的叔叔”(即x為它父節點的右孩子)                                          
04                if color[w] = RED                                           // Case 1: x是“黑+黑”節點,x的兄弟節點是紅色。(此時x的父節點和x的兄弟節點的子節點都是黑節點)。
05                   then color[w] ← BLACK ? Case 1   //   (01) 將x的兄弟節點設為“黑色”。
06                        color[p[x]] ← RED ? Case 1   //   (02) 將x的父節點設為“紅色”。
07                        LEFT - ROTATE(T, p[x]) ? Case 1   //   (03) 對x的父節點進行左旋。
08                        w ← right[p[x]] ? Case 1   //   (04) 左旋後,重新設定x的兄弟節點。
09                if color[left[w]] = BLACK and color[right[w]] = BLACK       // Case 2: x是“黑+黑”節點,x的兄弟節點是黑色,x的兄弟節點的兩個孩子都是黑色。
10                   then color[w] ← RED ? Case 2   //   (01) 將x的兄弟節點設為“紅色”。
11                        x ←  p[x] ? Case 2   //   (02) 設定“x的父節點”為“新的x節點”。
12                   else if color[right[w]] = BLACK                          // Case 3: x是“黑+黑”節點,x的兄弟節點是黑色;x的兄弟節點的左孩子是紅色,右孩子是黑色的。
13                           then color[left[w]] ← BLACK ? Case 3   //   (01) 將x兄弟節點的左孩子設為“黑色”。
14                                color[w] ← RED ? Case 3   //   (02) 將x兄弟節點設為“紅色”。
15                                RIGHT - ROTATE(T, w) ? Case 3   //   (03) 對x的兄弟節點進行右旋。
16                                w ← right[p[x]] ? Case 3   //   (04) 右旋後,重新設定x的兄弟節點。
17                         color[w] ← color[p[x]] ? Case 4   // Case 4: x是“黑+黑”節點,x的兄弟節點是黑色;x的兄弟節點的右孩子是紅色的。(01) 將x父節點顏色 賦值給 x的兄弟節點。
18                         color[p[x]] ← BLACK ? Case 4   //   (02) 將x父節點設為“黑色”。
19                         color[right[w]] ← BLACK ? Case 4   //   (03) 將x兄弟節點的右子節設為“黑色”。
20                         LEFT - ROTATE(T, p[x]) ? Case 4   //   (04) 對x的父節點進行左旋。
21                         x ← root[T] ? Case 4   //   (05) 設定“x”為“根節點”。
22        else (same as then clause with "right" and "left" exchanged)        // 若 “x”是“它父節點的右孩子”,將上面的操作中“right”和“left”交換位置,然後依次執行。
23 color[x] ← BLACK


詳細內容看這裡