1. 程式人生 > >演算法導論——紅黑樹插入演算法C++實現

演算法導論——紅黑樹插入演算法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)的時間內完成節點查詢和插入。