演算法導論——紅黑樹插入演算法C++實現
一、概念
紅黑樹是一棵二叉搜尋樹,它在每個結點上增加了一個儲存位來表示結點的顏色,可以是RED或BLACK。通過對任何一條從根到葉子的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑會比其他路徑長2倍,因而是近似於平衡的。
二、定義
一棵紅黑樹是滿足下面紅黑性質的二叉搜尋樹:
1、每個結點或是紅色,或是黑色;
2、根節點是黑色的;
3、每個葉節點(NIL)是黑色的;
4、如果一個結點是紅色的,則它的兩個子結點都是黑色的;
5、對每個結點,從該結點到其所有後代葉節點的簡單路徑上,均包含相同數目的黑色節點(此黑色結點的數目稱為黑高)。
三、和平衡二叉樹的區別
平衡二叉樹是完全平衡的,而紅黑樹是區域性平衡的,能確保沒有一條路徑會比其他路徑長2倍,並且調整少,效能高,所以得到廣泛應用,在STL裡map和set都由紅黑樹實現。
四、資料結構
#pragma once #define RED 0 #define BLACK 1 struct Node{ Node(int key){ this->key=key; } int key; int color; Node* parent; Node* left; Node* right; }; struct Tree{ Tree(){ } Node* root; Node* nil; };
五、示例(圖中所有空指標指向葉結點NIL)
六、旋轉
目的:對紅黑樹進行插入和刪除,可能會導致該樹不在滿足紅黑樹的性質,為了維護紅黑樹的性質,必須要改變樹中某些結點的顏色以及指標結構。改變指標結構即通過旋轉來完成,這是一種能保持二叉搜尋樹性質的搜尋樹區域性操作。
6.1 左旋(LEFT_ROTATE)
圖示:由三部完成該操作。
程式碼:
//節點左旋 void LEFT_ROTATE(Tree* &T, Node* x){ Node* y; y=x->right; x->right=y->left; if(y->left!=T->nil) y->left->parent=x; y->parent=x->parent; if(x->parent==T->nil) T->root=y; else if(x==x->parent->left) x->parent->left=y; else x->parent->right=y; y->left=x; x->parent=y; }
6.2 右旋(LEFT_ROTATE)
圖示:由三部完成該操作。
程式碼:
七、插入//節點右旋 void RIGHT_ROTATE(Tree* &T, Node* y){ Node *x; x=y->left; y->left=x->right; if(x->right!=T->nil) x->right->parent=y; x->parent=y->parent; if(y->parent==T->nil) T->root=x; else if(y==y->parent->left) y->parent->left=x; else y->parent->right=x; x->right=y; y->parent=x; }
插入操作與上一篇博文二叉排序樹插入操作基本相同,除了細節之處稍有改變。
程式碼:
//插入節點 void RB_INSERT(Tree* &T,Node* z){ Node* y=T->nil; Node* x=T->root; while(x!=T->nil){ y=x; if(z->key < x->key) x=x->left; else x=x->right; } z->parent=y; if(y==T->nil)//插入第一個元素 T->root=z; else if(z->key < y->key) y->left=z; else y->right=z; z->left=T->nil; z->right=T->nil; z->color=RED; RB_INSERT_FIXUP(T,z); }
八、染色和調整
由於插入操作可能破壞紅黑樹性質,通過RB_INSERT_FIXUP(T,z)函式調整結點顏色和樹的結構,使其保持紅黑性質。
//紅黑樹調整 void RB_INSERT_FIXUP(Tree* &T, Node* z){ Node* y; while(z->parent->color==RED){ if(z->parent==z->parent->parent->left){ //z節點父節點為其祖父節點的左孩子 y=z->parent->parent->right; if(y->color==RED){ //case1 z->parent->color=BLACK; y->color=BLACK; z->parent->parent->color=RED; z=z->parent->parent; } else{ if(z==z->parent->right){ //case2 z=z->parent; LEFT_ROTATE(T,z); } z->parent->color=BLACK; //case3 z->parent->parent->color=RED; RIGHT_ROTATE(T,z->parent->parent); } } else{ //z節點父節點為其祖父節點的右孩子 y=z->parent->parent->left; if(y->color==RED){ //case 1 z->parent->color=BLACK; y->color=BLACK; z->parent->parent->color=RED; z=z->parent->parent; } else{ if(z==z->parent->left){ //case2 z=z->parent; RIGHT_ROTATE(T,z); } z->parent->color=BLACK; //case3 z->parent->parent->color=RED; LEFT_ROTATE(T,z->parent->parent); } } } T->root->color=BLACK; }
九、測試函式
十、實驗總結int main(){ Tree* T; Node* z[MAXSIZE]; int arr[MAXSIZE]={12,1,9,2,0,11,7,19,4,15, 18, 5, 14, 13, 10, 16, 6, 3, 8, 17}; T=new Tree(); T->nil=new Node(0); T->nil->color=BLACK; T->root=T->nil; for(int i=0;i<MAXSIZE;i++){ z[i]=new Node(arr[i]); RB_INSERT(T,z[i]); } PreOrder(T->root,T); cout << endl; InOrder(T->root,T); return 0; }
本次實驗紅黑樹插入演算法實現中遇到了很多bug,最難以察覺的bug是在節點旋轉的過程中,很可能把根節點旋丟了,經過多次分析和變數追蹤,最後採取的方法是:為紅黑樹單獨建立一個結構體,專門用來跟蹤根節點和nil節點,最終問題得到完美解決,另外試驗中還出現的錯誤包括邊界條件處理,尤其是給空節點指定父指標這樣的錯誤,還有if條件語句中誤把“==”比較運算子,寫為“=”賦值運算子,最終通過資料測試和斷點追蹤順利找出錯誤。另外經過實驗發現紅黑樹是一種效率比較高的樹,可在O(logn)的時間內完成節點查詢和插入。