1. 程式人生 > >演算法導論 第13章 紅黑樹(圖文詳細解說)

演算法導論 第13章 紅黑樹(圖文詳細解說)

1、二叉查詢樹的不足

二叉查詢樹的基本操作包括搜尋、插入、刪除、取最大和最小值等都能夠在O(h)(h為樹的高度)時間複雜度內實現,因此能在期望時間O(lgn)下實現,但是二叉查詢樹的平衡性在這些操作中並沒有得到維護,其高度可能會變得很高,當其高度較高時,二叉查詢樹的效能就未必比連結串列好了,所以二叉查詢樹的集合操作是期望時間O(lgn),最壞情況下為O(n)。由於二叉查詢樹的高度問題,因而出現了紅黑樹,紅黑樹能夠保證在最壞情況下的集合操作時間都是O(lgn),紅黑樹因其效能較好,有很多的應用,例如在C++STL中的一些效能較好的容器(如:set,map)都是利用紅黑樹作為底層資料結構來支撐的。

2、紅黑樹

紅黑樹也是一種二叉查詢樹,它擁有二叉查詢樹的性質,同時紅黑樹還有其它一些特殊性質,這使得紅黑樹的動態集合基本操作在最壞情況下也為O(lgn),紅黑樹通過給節點增加顏色和其它限制條件,使得紅黑樹中的某一節點到葉節點的任一路徑不會比其它路徑長兩倍,因此可以保持樹的平衡性。紅黑樹中節點包括五個域:顏色、關鍵字、左節點、右節點和父節點,並將沒有子節點或父節點的指標指向一個 nil 哨兵節點,這個nil節點稱著葉節點,也是外節點,其它包含關鍵字的節點叫著內節點。之所以要增加這樣一個為空的外節點,是為了方便紅黑樹上的一些邊界操作,特別是在刪除節點的時候,這個nil節點就很有用了。

紅黑樹的節點定義為:

color key left right parent

2.1、紅黑樹的性質

紅黑樹的五個性質:

1、每個節點或者是紅的,或者是黑的。

2、根節點是黑的。

3、葉節點(nil節點)是黑的。

4、如果一個節點是紅的,則其孩子節點都是黑的。

5、對於任意節點,從該節點到其子孫葉節點(nil節點)的所有路徑上包含相同數目的黑節點。

如下圖:


那麼為什麼紅黑樹要定義這五個性質呢?其根本原因就是為了讓紅黑樹能夠自平衡,保證紅黑樹的高度為O(lgn),下面分別解釋一下這五條性質:

性質1:定義了節點的顏色,是為後面的性質做鋪墊的,不用細說。

性質2:一定要定義根節點為黑色,因為後面有性質4,定義根節點為黑色,則根節點的子節點的顏色就可以為紅,也可以為黑,沒有受到限制,否則如果定義根節點為紅,則其子節點就只能為黑,就是實說紅黑樹的上兩層節點顏色就定死了,這樣顯然讓樹不夠靈活。

性質3:定義外節點nil節點為黑色,這時為紅黑樹操作邊界情況時提供方便,大家在看到後面的刪除節點的時候就會發現,如果定義為紅色,若刪除的節點在樹的最底層,邊界處理就能回考慮得更麻煩。

性質4:很重要,它說明了紅黑樹中如果有紅節點,其子孫一定為黑節點,但是有黑節點,不一定要出現紅節點,也就保證了整個紅黑樹中的任意路徑上黑節點的個數至少是路徑上節點總個數的一半,這為保證紅黑樹的高度任何時刻都為O(lgn)提供了重要依據。

性質5:就是為了讓紅黑樹保持平衡,不出現普通二叉查詢樹可能出現的一邊倒的情況。

因此紅黑樹的每個性質都是有特別意義的。

黑高度:從某個節點(不包括該節點)出發到達葉節點(nil節點)的任意一條路徑上,黑節點的個數稱為該節點的黑高度bh(x)。紅黑樹的黑高度就定義為根節點的黑高度。

引理:一顆有n個內節點的紅黑樹的高度至多為2lg(n+1)(可以利用節點的黑高度來證明),這也就保證了最壞情況下查詢操作 O(lgn) 的時間複雜度。

這個引理很重要,它的得出就是由上面的五條性質支援的。下面我給出簡單的證明:

設根節點為x,其黑高度為bh(x),則這棵紅黑樹節點最少的情況就是:高度h = bh(x) - 1,因為 bh(x) 包含了nil節點(為黑),所以路徑上黑節點的實際數目應該是bh(x) - 1 ,在最長路徑上沒有紅節點,全是黑節點,這時的節點總數是2^(h+1) - 1,也就是2^bh(x) - 1,所以紅黑樹的總結點數 n >= 2^bh(x) - 1,又因為紅黑樹的任意路徑上黑節點個數至少是總結點個數的一半,所以bh(x) >= h/2(h為樹的實際高度),所以n >= 2^(h/2) - 1,從而有h <= 2*lg(n+1),所以h = O(lgn)。

當然,這個引理也可以通過數學歸納法來證明,演算法導論中就是如此。

紅黑樹在執行一些搜尋,取最大最小值,取前驅後繼節點等操作沒有修改紅黑樹,因此跟二叉查詢樹上的操作相同,但是對於插入和刪除操作,紅黑樹由於被修改了,必須執行一些額外的操作來維護其紅黑樹的性質,實際上這些額外的操作也不會改變插入和刪除操作的時間複雜度,仍然為O(lgn)。

2.2、紅黑樹的旋轉

為了維護紅黑樹的性質,需要執行一些紅黑樹特有的操作:旋轉,包括左旋和右旋。

左旋:對節點x做左旋,必須保證它的右孩子y 不為nil節點,旋轉以x到y之間的支軸進行,讓y取代x的位置成為該子樹新的根,而x成為y的左孩子,y原來的左孩子就成為x的右孩  子。時間複雜度為O(1)。


右旋:與左旋相反,對節點x做右旋,必須保證它的左孩子y不為nil節點,旋轉以x到y的支軸進行,讓y取代x的位置成為該子樹新的根,而x成為y的右孩子,y原來的右孩子就成為  x的左孩子。時間複雜度為O(1)。


2.3、紅黑樹的插入和刪除

在對紅黑樹執行插入和刪除之後,需要執行額外的維護操作,這些操作就是建立在旋轉之上的。下面分析一下在紅黑樹中插入和刪除節點之後可能出現的問題,及解決思路:在插入或者刪除節點之後,維護紅黑樹的性質的時候,都是隻有一種情形可以直接通過變換來保持紅黑樹的性質,其它情形就必須通過變換轉化到這一種可以直接解決的出口情形。

1、插入節點

向紅黑樹中插入一個新的節點,為了不增加黑節點的個數(從而影響黑高度),我們將新插入的節點塗為紅色,這樣插入之後它可能會違反性質2和4,在未付性質2和4的同時還得注意不能違反性質5,主要考慮這三個性質即可。因此維護的主要任務就是通過修改紅黑樹中節點的結構和顏色讓根節點為黑色,或者讓插入的紅節點的父節點變成黑色(如果不是黑色)。

1)將新插入節點定為紅色(因為這樣不會增加黑節點,不會違反性質5),若違反紅黑樹的其它性質,則執行變換。

2)如果插入節點為根節點,則直接將其變為黑色即可;若不是根節點,則逐步修正,將違反規則的位置上移直到變成出口情形來直接解決。共分為三種情況:

因為當前插入節點為紅,若父節點也為紅,則違反性質4

(1)當前節點的叔叔節點為紅,則祖父節點必然為黑色,可以將父節點和叔叔節點都塗黑,祖父節點塗紅,然後將祖父節點變成當前節點,這樣違規位置就上升了,迴圈演算法。(祖父節點必須塗紅,是為了保證黑高度保持不變,否則對上面的節點會產生影響)


(2)當前節點的叔叔節點為黑

(2.1)當前節點為其父節點的左孩子(出口情形)

可以將父節點塗黑,祖父節點塗紅,然後沿祖父節點右旋,則當前節點的父節點變成了黑色,性質4滿足了,同時性質5沒有被破壞,維護完成,演算法結束。


(2.2)當前節點為其父節點的右孩子

這種情形需要通過變換轉換成情形2.1,以方便解決。即:將當前節點的父節點作為新的當前節點,以新的當前節點為支點左旋,從而轉換成了情形2.1。


實際上,維護插入節點後紅黑樹的性質共分為三種情形:主要考慮插入節點的叔叔節點,如果叔叔節點為紅,則通過不斷上移違規位置直到叔叔節點為黑來解決,或者直接變成了根節點,根節點直接就塗黑了;如果叔叔節點為黑,這時候又分兩種情形,如果當前節點是其父節點的左孩子,則可以直接解決,如果是右孩子,則變換成是左孩子的情形在解決。

2、刪除節點

從紅黑樹中刪除一個節點之後,如果這個節點是紅的,則紅黑性質沒有被破壞,因為被刪除的是紅節點,所以不是根節點,性質2沒問題,同時黑節點沒有變,所以性質5沒問題,因為被刪除節點的父節點是黑,所以無論替補上去的節點是什麼顏色都不會破壞性質4,性質1和3沒有被影響,所以所有性質都沒有破壞。如果被刪除的節點是黑色的,則節點的黑高度變化了,性質5被破壞了,維護的主要任務就是通過修改節點的結構和顏色從而增加該子樹一個黑高度。

在刪除節點之後,主要考慮替補節點的兄弟節點,把替補節點作為當前節點,如果當前節點為紅,則直接塗黑即可保持性質;如果當前節點為黑色,若被刪除的節點為黑色,則當前節點所在子樹的黑高度少了1,如果當前節點為根節點,則整個樹的黑高度少1,沒有影響,否則就要變換調整,同插入修正一樣,將黑高度少1的違規位置上升,直到進入出口情形,直接解決。共有4種情形。如果刪除的節點沒有孩子,則替補節點就是nil外節點,保證了替補節點不為NULL,同時為黑色,方便操作。

1)當前節點為黑,兄弟節點為紅

將這種情形轉換成兄弟節點為黑的情形來處理,即:將當前節點的父節點塗紅,兄弟節點塗黑,,以父節點為支點左旋,則當前節點的兄弟節點就為原來兄弟節點的左黑孩子,變成了兄弟節點為黑的情形。


2)當前節點的兄弟節點為黑

(2.1)兄弟節點的兩個孩子全為黑

把兄弟節點塗紅,讓父節點成為新的當前節點,使使父節點為根的整個子樹黑高度少1,違規位置上移。(藍色代表任意顏色)


(2.2)兄弟節點的左孩子為紅,右孩子為黑

將這種情形轉換成兄弟節點的右孩子為紅的情形來解決,即:將兄弟節點塗紅,並將其左孩子塗黑,然後以兄弟節點為支點右旋,則當前節點的新的兄弟節點為元兄弟節點的左孩子,且想在兄弟節點的右孩子為紅了。


(2.3)兄弟節點的右孩子為紅,左孩子任意(出口情形)

這時出口情形,可以直接解決,可以先把兄弟節點塗成父節點的顏色,再把父節點和兄弟節點的右孩子塗黑,然後以父節點為支點左旋,此時所有性質都滿足了;因為原本是當前節點的黑高度比兄弟節點少1,只與兄弟節點的孩子相同,經過這次變換,當前節點所在的位置變成了原來的黑父節點,黑高度增加了1,所以保持了性質5,而其它節點的黑高度也沒有受到影響,依然沒變。所以維護完成,演算法結束。


/*
 *	演算法導論 第十三章 紅黑樹
 */

#include <iostream>
using namespace std;

//定義顏色
enum Color
{
	BLACK = 0,
	RED = 1
};
//定義紅黑樹節點
typedef struct RBTNode
{
	Color color;
	int key;
	RBTNode *left, *right, *parent;

	RBTNode()
	{
		this->color = Color::RED;
		this->key = 0;
		this->left = this->right = this->parent = NULL;
	}

	RBTNode(Color c, int k, RBTNode* l, RBTNode* r, RBTNode* p)
	{
		this->color = c;
		this->key = k;
		this->left = l;
		this->parent = p;
		this->right = r;
	}

}RBTNode, RBTree;

//定義一個哨兵nilNode,以方便處理邊界問題
//特別是在刪除紅黑樹元素時很有用
// 如果刪除節點沒有孩子,nilNode作為其孩子處理起來就方便多了
RBTNode* nilNode = NULL;

/*
 *	中序遍歷
 *	遞迴
 */
void inOrderTreeWalkRecursion(RBTree* tree)
{
	if (tree && tree != nilNode)
	{
		inOrderTreeWalkRecursion(tree->left);
		cout<<tree->key<<" ";
		inOrderTreeWalkRecursion(tree->right);
	}
}

/*
 *	查詢二叉排序樹中的最小元素
 *	即為最左元素
 *	時間複雜度為O(lgn)
 */
RBTNode* rbTreeMinimum(RBTree* tree)
{
	if (! tree || tree == nilNode)
		return NULL;

	while (tree->left != nilNode)
	{
		tree = tree->left;
	}

	return tree;
}

/*
 *	求二叉排序樹中指定節點node的中序遍歷後繼
 *	如果node右子樹不為空,則後繼則為node右子樹中的最小節點
 *	否則node右子樹為空,必須向上回溯找到第一個節點:該節點為其父節點的左孩子
 *	後繼即為其父節點,如果不存在這樣的節點,說明node為最右節點,後繼為空
 *	時間複雜度為O(lgn)
 */
RBTNode* rbTreeSuccessor(RBTNode* node)
{
	if (! node || node == nilNode)
		return NULL;

	if (node->right != nilNode)
	{
		return rbTreeMinimum(node->right);
	}

	RBTNode* temp = node->parent;
	while (temp != nilNode && node == temp->right)
	{
		node = temp;
		temp = node->parent;
	}
	return temp;
}

/*
 *	紅黑樹節點左旋
 */
void leftRotate(RBTree* &tree, RBTNode* node)
{
	//如果node為空或者其右節點為空,就返回
	if (! node || node == nilNode || node->right == nilNode)
		return;
	//讓node右節點的左孩子成為node的右孩子
	RBTNode* temp = node->right;
	node->right = temp->left;
	if (temp->left != nilNode)
	{
		temp->left->parent = node;
	}
	//讓node的右孩子取代node的位置
	temp->parent = node->parent;
	if (node->parent == nilNode)
	{//說明node是根節點,修改根節點的指標
		tree = temp;
	} else {
		if (node == node->parent->left)
		{
			node->parent->left = temp;
		} else {
			node->parent->right = temp;
		}
	}
	//讓node成為其右孩子的左孩子
	temp->left = node;
	node->parent = temp;
}

/*
 *	紅黑樹節點右旋
 */
void rightRotate(RBTree* &tree, RBTNode* node)
{
	//如果node為空或者其左節點為空,就返回
	if (! node || node == nilNode || node->left == nilNode)
		return;
	//讓node左節點的右孩子成為node的左孩子
	RBTNode* temp = node->left;
	node->left = temp->right;
	if (temp->right != nilNode)
	{
		temp->right->parent = node;
	}
	//讓node的右左孩子取代node的位置
	temp->parent = node->parent;
	if (node->parent == nilNode)
	{//說明node是根節點,修改根節點的指標
		tree = temp;
	} else {
		if (node == node->parent->left)
		{
			node->parent->left = temp;
		} else {
			node->parent->right = temp;
		}
	}
	//讓node成為其左孩子的右孩子
	temp->right = node;
	node->parent = temp;
}

/*
 *	紅黑樹插入元素後,對樹進行調整以保持紅黑樹的性質
 *	主要耗時在將違規的位置上升,因為高度為O(lgn),所以其時間複雜度依然為O(lgn)
 *	而旋轉一共不超過2次,每次時間複雜度為O(1)
 *	所以總的時間複雜度為O(lgn)
 */
void rbInsertFixup(RBTree* &tree, RBTNode* node)
{
	if (! tree || ! node)
		return;

	//	因為新插入的node顏色為紅,只可能違反性質2(根節點為黑色)和性質4(紅節點的孩子節點只能為黑)
	//	所以如果node的父節點不是nilNode,則只有在其父節點也為紅的時候才違反紅黑樹的性質,需要調整
	//	否則不用調整
	while (node->parent->color == Color::RED)
	{	//主要考慮node的叔叔節點
		RBTNode* uncleNode = NULL;
		if(node->parent == node->parent->parent->left)
		{
			uncleNode = node->parent->parent->right;

			if (uncleNode->color == Color::RED)
			{
				// 情形1:node的叔叔節點為紅,則將違規位置上移
				// 因為node的父節點和叔叔節點均為紅,則node的祖父節點必然為黑
				// 因此可以將node的父節點和叔叔節點均變成黑,而祖父節點變成紅
				// 這樣依然保持了整體的黑高度沒變,保持了性質5(任意節點到其子孫節點的路徑上黑節點個數相同)
				node->parent->color = Color::BLACK;
				uncleNode->color = Color::BLACK;
				node->parent->parent->color = Color::RED;
				node = node->parent->parent;
			} else {
				//	情形2:node的叔叔節點為黑

				// 情形2.1:node為其父節點的右孩子,將情形2.1轉化成2.2以便處理,因為node和其父節點都是紅的,所以演父節點旋轉沒有<span style="white-space:pre">				</span>   影響
				if (node == node->parent->right)
				{
					node = node->parent;
					leftRotate(tree, node);//從2.1變成了2.2
				}

				//	情形2.2:node為其父節點的左孩子,這種情形為出口情形
				//	因為父節點為紅,叔叔節點為黑,則祖父節點一定為黑,所以將父節點變為黑
				//	祖父節點變為紅,再沿祖父節點右旋則黑高度沒變,同時node的父節點變黑了
				//	達到主要目的:讓node的父節點變成黑色,以保持性質4
				// 在這個過程中又不能改變黑高度,因為要保持性質5
				node->parent->color = Color::BLACK;
				node->parent->parent->color = Color::RED;
				rightRotate(tree, node->parent->parent);
			}

		} else {
			//叔叔節點的位置相反,處理方法同上,只是旋轉時候的方向相反
			uncleNode = node->parent->parent->left;

			if (uncleNode->color == Color::RED)
			{
				// 情形1:node的叔叔節點為紅,則將違規位置上移
				node->parent->color = Color::BLACK;
				uncleNode->color = Color::BLACK;
				node->parent->parent->color = Color::RED;
				node = node->parent->parent;
			} else {
				//	情形2:node的叔叔節點為黑

				// 情形2.1:node為其父節點的左孩子
				if (node == node->parent->left)
				{
					node = node->parent;
					rightRotate(tree, node);//從2.1變成了2.2
				}

				//	情形2.2:node為其父節點的右孩子,這種情形為出口情形
				node->parent->color = Color::BLACK;
				node->parent->parent->color = Color::RED;
				leftRotate(tree, node->parent->parent);
			}
		}

	}

	//保持性質2,讓根節點為黑
	tree->color = Color::BLACK;
}

/*
 *	插入元素
 *	搜尋插入點時間為O(lgn)
 *	維護紅黑樹的性質時間為O(lgn)
 *	所以總的時間複雜度為O(lgn)
 */
void rbInsert(RBTree* &tree, RBTNode* node)
{
	if (! tree || ! node)
		return;

	RBTNode* posNode = nilNode;
	RBTNode* t = tree;
	while (t != nilNode)
	{
		posNode = t;
		if (node->key < t->key)
		{
			t = t->left;
		} else {
			t = t->right;
		}
	}

	node->parent = posNode;

	if (posNode == nilNode)
	{
		tree = node;
	} else {
		if (node->key < posNode->key)
		{
			posNode->left = node;
		} else {
			posNode->right = node;
		}
	}
	//不同於二叉排序樹的地方
	node->left = node->right = nilNode;
	node->color = Color::RED;//插入紅節點以保證黑高度不變
	//維護紅黑樹性質
	rbInsertFixup(tree, node);
}

/*
 *	刪除紅黑樹節點後維護紅黑樹的性質
 *	主要耗時仍然是將違規位置上升,時間複雜度為O(lgn)
 *	旋轉最多3次,共O(1)
 *	所以時間複雜度為O(lgn)
 */
void rbDeleteFixup(RBTree* &tree, RBTNode* node)
{
	//如果node是根節點,則整個樹的黑高度都減一,沒有影響
	//如果node的顏色是紅色,則直接塗黑就可以了
	// 否則就要維護了,紅黑樹節點刪除之後,維護性質主要與node的兄弟節點有關
	while (node != tree && node->color == Color::BLACK)
	{
		//當前節點node為非根黑節點,考慮其兄弟節點
		RBTNode* brotherNode = NULL;
		if (node == node->parent->left)
		{
			brotherNode = node->parent->right;
			//情形1:兄弟節點為紅,將其轉換成兄弟節點為黑的情形2
			//應為node節點為黑,兄弟節點為紅,則其父節點必然為黑
			// 且兄弟節點的子節點必然為黑,因此可以將父節點變紅,兄弟節點變黑
			// 然後沿父節點左旋,則黑高度沒有被影響,同時node的新的兄弟節點變成了原兄弟節點的左黑孩子了
			// 因此node的兄弟節點變為黑節點了
			if (brotherNode->color == Color::RED)
			{
				node->parent->color = Color::RED;
				brotherNode->color = Color::BLACK;
				leftRotate(tree, node->parent);
				brotherNode = node->parent->right;
			}

			//情形2:兄弟節點為黑

			if (brotherNode->right->color == Color::BLACK && brotherNode->left->color == Color::BLACK)
			{
				//情形2.1:兄弟節點的右孩子為黑,且其左孩子為黑
				//這時node為黑,其兄弟節點和兩個孩子節點也均為黑
				//因為此時node子樹比其兄弟節點的子樹黑高度小1,所以可以把兄弟節點變紅,從而二者黑高度相同
				// 從而讓node的父節點子樹整個黑高度降低1(違反規則5),讓node的父節點變成新的node,使違規的位置上升
				// 繼續迴圈
				brotherNode->color = Color::RED;
				node = node->parent;

			} else {
				if (brotherNode->right->color == Color::BLACK) {
					//情形2.2:兄弟節點的右孩子為黑,且其左孩子為紅,將這種情形轉換成2.3
					//因為兄弟節點為黑,其左孩子為紅,右孩子為黑,因此可以將兄弟節點的左孩子變黑
					//兄弟節點變紅,然後沿兄弟節點右旋,從而進入情形2.3,且為改變黑高度
					brotherNode->left->color = Color::BLACK;
					brotherNode->color = Color::RED;
					rightRotate(tree, brotherNode);
					brotherNode = node->parent->right;
				}

				//情形2.3:兄弟節點的右孩子為紅,左孩子任意,這時出口情形,經過以下變換,演算法結束
				// 因為兄弟節點為黑,但其右孩子為紅,可以把兄弟節點變成父節點的顏色,把其右孩子和node父節點變黑
				// 然後沿node父節點左旋,則node的兄弟節點的位置仍然是黑色(為brother的右孩子),該子樹的黑高度不變
				// 而node節點所在子樹因為增加了node父節點這個黑節點而黑高度增加1,所以兩邊的黑高度相同了
				// (原來node子樹比其兄弟子樹黑高度少1),從而保持了性質5
				brotherNode->color = node->parent->color;
				node->parent->color = Color::BLACK;
				brotherNode->right->color = Color::BLACK;
				leftRotate(tree, node->parent);

				//問題已解決,讓node指向根節點,從而退出迴圈
				node = tree;
			}

		} else {//兄弟節點的位置相反,原理同上
			brotherNode = node->parent->left;
			//情形1:兄弟節點為紅,將其轉換成兄弟節點為黑的情形2
			if (brotherNode->color == Color::RED)
			{
				node->parent->color = Color::RED;
				brotherNode->color = Color::BLACK;
				rightRotate(tree, node->parent);
				brotherNode = node->parent->left;
			}

			//情形2:兄弟節點為黑

			if (brotherNode->left->color == Color::BLACK && brotherNode->right->color == Color::BLACK)
			{
				//情形2.1:兄弟節點的左孩子為黑,且其右孩子為黑
				brotherNode->color = Color::RED;
				node = node->parent;
			} else {
				if (brotherNode->left->color == Color::BLACK) {
					//情形2.2:兄弟節點的左孩子為黑,且其右孩子為紅,將這種情形轉換成2.3
					brotherNode->right->color = Color::BLACK;
					brotherNode->color = Color::RED;
					leftRotate(tree, brotherNode);
					brotherNode = node->parent->left;
				}

				//情形2.3:兄弟節點的左孩子為紅,右孩子任意,這時出口情形,經過以下變換,演算法結束
				brotherNode->color = node->parent->color;
				node->parent->color = Color::BLACK;
				brotherNode->left->color = Color::BLACK;
				rightRotate(tree, node->parent);

				//問題已解決,讓node指向根節點,從而退出迴圈
				node = tree;
			}
		}
	}

	node->color = Color::BLACK;
}

/*
 *	刪除紅黑樹節點
 *	主要耗時是在獲取刪除節點的中序遍歷後繼節點,時間複雜度為O(lgn)
 *	而維護紅黑樹性質的時間複雜度為O(lgn)
 *	所以總的時間複雜度為O(lgn)
 */
RBTNode* rbDelete(RBTree* &tree, RBTNode* node)
{
	if (! tree || ! node)
		return;

	RBTNode* delNode = NULL;
	if (node->left == nilNode || node->right == nilNode)
	{
		delNode = node;
	} else {
		delNode = rbTreeSuccessor(node);
	}

	RBTNode* fillNode = NULL;
	if (delNode->left != nilNode)
	{
		fillNode = delNode->left;
	} else {
		fillNode = delNode->right;
	}

	fillNode->parent = delNode->parent;

	if (delNode->parent == nilNode)
	{
		tree = fillNode;
	} else {
		if (fillNode == fillNode->parent->left)
		{
			fillNode->parent->left = fillNode;
		} else {
			fillNode->parent->right = fillNode;
		}
	}

	if (delNode != node)
	{
		node->key = delNode->key;
	}

	//如果被刪除節點是黑色,則樹中某些的黑高度被減一,必須維護性質5
	//將該節點相關路徑上增加一個黑節點
	if (delNode->color == Color::BLACK)
	{
		rbDeleteFixup(tree, fillNode);
	}

	return delNode;
}