在之前的篇章當中我們已經了解了基于二叉搜索樹的AVL樹,那么接下來在本篇當中將繼續來學習另一種基于二叉搜索樹的樹狀結構——紅黑樹,在此和之前學習AVL樹類似還是通過先了解紅黑樹是什么以及紅黑樹的結構特點,接下來在試著實現紅黑樹的結構以及實現紅黑樹插入新節點、進行節點查詢的功能,相信通過本篇的學習能讓你了解紅黑樹,一起加油把!!!
1. 紅黑樹的概念
在此紅黑樹是基于二叉搜索樹進行改進的,因此紅黑樹的中序遍歷也是有序的。
紅黑樹是?棵二叉搜索樹,他的每個結點增加?個存儲位來表示結點的顏色,可以是紅色或者黑色。通過對任何?條從根到葉子的路徑上各個結點的顏?進行約束,紅黑樹確保沒有?條路徑會比其他路徑長出2倍,因而是接近平衡的。
1.1 紅黑樹的規則
只有同時滿足以下的幾點要求時才是在紅黑樹:
1. 每個結點不是紅色就是黑色
2. 根結點是黑色的
3. 如果?個結點是紅色的,則它的兩個孩?結點必須是黑色的,也就是說任意?條路徑不會有連續的紅色結點。
4. 對于任意?個結點,從該結點到其所有NULL結點的簡單路徑上,均包含相同數量的黑色結點
以上的要求看起來是規律的,但是其實這幾點規則之間是相互協調的,接下來我們就通過幾個示例來看看這些規則是怎么使得紅黑樹當中的最長路徑的長度不大于其他路徑的兩倍的。?
來看以下的示例:
以上圖示的二叉樹當中,根節點為黑,并且不存在連續的紅節點,那么接下來就是要知道二叉樹當中每個路徑上黑節點的個數;在此之前需要我們先找出以上的二叉樹有幾條路徑,可能你會簡單的認為以上不就是有4條路徑嗎?如下所示
但是其實以上求路徑個數的方式是錯誤的,在此要一條路徑的需要到空節點為止,因此以上的正確的路徑應如下所示:
?
此時就可以看出以上的二叉樹有9條路徑,并且每條的路徑上黑色節點的個數都是2兩個,這也是滿足紅黑樹的要求的。
以上的二叉樹當中滿足了以上所示的紅黑樹的1、2、4點規則,但是對應規則3以上的所示的二叉樹的葉子節點為紅時不就不滿足規則了嗎?
這其實在在此通常情況下我們會忽略這種情況,在《算法導論》等書籍上補充了?條每個葉?結點(NIL)都是黑色的規則。他這?所指的葉子結點不是傳統的意義上的葉子結點,而是我們說的空結點,有些書籍上也把NIL叫做外部結點。NIL是為了方便準確的標識出所有路徑,《算法導論》在后續講解實現的細節中也忽略了NIL結點,所以我們知道?下這個概念即可。
那么在滿足紅黑樹的規則下,就可以使得沒有?條路徑會比其他路徑長出2倍,因而是接近平衡的。
以上所示的紅黑樹當中最長路徑為3,最短的為2,這時二叉搜索樹就是接近平衡的
以下的二叉搜索樹也是滿足以上的紅黑樹規則,也是紅黑樹
?這是就可以看出紅黑樹當中其實是完全沒有紅色節點的,這是也是滿足紅黑樹的規則的
1.2 紅黑樹如何保持接近平衡?
在此我們就要思考在紅黑樹是如何確保基本平衡的,也就是在紅黑樹當中是如何確保最長的路徑不超過最短路徑的兩倍的?
? 由規則4可知,從根到NULL結點的每條路徑都有相同數量的黑色結點,所以極端場景下,最短路徑就就是全是??結點的路徑,假設最短路徑長度為bh(black height)。
? 由規則2和規則3可知,任意?條路徑不會有連續的紅色結點,所以極端場景下,最長的路徑就是一黑一紅間隔組成,那么最長路徑的長度為2*bh。
? 綜合紅黑樹的4點規則而言,理論上的全?最短路徑和一黑一紅的最長路徑并不是在每棵紅?樹都存在的。假設任意?條從根到NULL結點路徑的?度為x,那么bh <= h <= 2*bh。
1.3 紅黑樹的效率?
以上我們了解了紅黑樹的概率,以及要使得二叉搜索樹為紅黑樹要滿足什么樣的要求,那么接下來就來分析在紅黑樹當中進行查找的效率。
假設N是紅?樹樹中結點數量,h最短路徑的長度,那么 , 由此推出
h ≈ logN ,也就是意味著紅?樹增刪查改最壞也就是走最長路徑為 2*logN,那么時間復雜度就為
?
在此紅黑樹其實相比之前學習的AVL樹控制平衡的方式不用,AVL樹是通過子樹的控制高度差進行整體的平衡控制,而紅黑樹是通過4條規則的顏色約束間接的實現近似平衡,他們效率都是同?檔次,但是相對而言,插?相同數量的結點,紅黑樹的旋轉次數是更少的,因為他對平衡的控制沒那么嚴格。在大量節點時AVL樹的高度相比紅黑樹會低一些。
2. 紅黑樹實現
以上我們了解了紅黑樹的結構特點之后接下來就來實現紅黑樹的代碼
在此創建兩個文件BRTRee.h和test.cpp,在BRTRee.h內實現紅黑樹的結構以及各種的功能,在test.cpp內對實現的紅黑樹進行測試,看是否滿足我們的要求
2.1 紅黑樹節點實現
在實現紅黑樹的結構之前我們先要實現紅黑樹節點的結構體,在此創建一個名為colour的枚舉來表示節點的顏色,創建一個名為RBTree的struct結構體來表示紅黑樹的節點,實現代碼如下所示:
enum colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{pair<K, V> _val;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;colour _col;RBTreeNode(const pair<K, V>& val):_val(val), _left(nullptr), _right(nullptr), _parent(nullptr){}};
2.2 紅黑樹結構實現?
在實現了紅黑樹節點的結構之后,接下來我們抽獎一個名為RBTree的類,在該類內實現紅黑樹的結構以及各種功能,接下來就先實現結構,實現的代碼如下所示:
template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;public://各種功能……private:Node* root = nullptr;};
以上我們時使用類模板的方式來創建對應的二叉樹,這樣就可以使得創建的二叉樹內節點的數據的類型是任意的,不過要存儲在紅黑樹內的數據類型是需要能進行大小的比較,如果默認不支持也是需要我們自己實現的。
2.3 紅黑樹插入實現
以上實現了紅黑樹的大體結構之后接下來就來實現紅黑樹當中的插入功能,接下來在實現插入的代碼之前先來分析在紅黑樹當中插入新的節點會有幾種情況。
在插入新的節點之后,新形成的也是要滿足紅黑樹的4條規則的。
1.在空樹當中插入節點,那么新增的節點需要是黑色節點。
在非空的樹當中插入新的節點,此時我們就要思考應將新的節點的顏色置為黑的還是紅色?
如果我們插入的節點是紅色的那么就只需要在其父節點為紅色是進行調整,但是如果插入的節點為黑色的,這就會使得新產生的路徑內的黑色節點數比其他的路徑要多一個,這時要進行調整是很困難的,因此插入的節點應該初始化為紅節點
2.在非空樹當中插入新的節點,將該節點初始化為紅
在非空樹內插入新節點之后接下來就要看其父節點是否為紅,是的話此時的樹就違背了紅黑樹的規則3,就需要進行調整。調整直到沒有應該紅色節點的父節點為紅為止。如果一開始插入的節點的發節點就為黑;那么插入完就可以停止操作
以下就是實現的不包含調整調整代碼的插入代碼:
bool Insert(const pair <K, V> kv)
{//當插入節點時的樹為空時就將新插入的節點置為樹的根節點,且顏色變為黑if (root == nullptr){root = new Node(kv);root->_col = BLACK;return true;}//尋找合適的插入位置Node* cur = root;Node* Parent = nullptr;while (cur){if (cur->_val.first < kv.first){Parent = cur;cur = cur->_right;}else if (cur->_val.first > kv.first){Parent = cur;cur = cur->_left;}else{return false;}}//創建新節點cur = new Node(kv);if (Parent->_val.first < kv.first){Parent->_right = cur;}else{Parent->_left = cur;}cur->_parent = Parent;cur->_col = RED;//進行調整使得樹滿足紅黑樹的規則……//無論以上是否進行調整都將根結點的顏色置為黑root->_col = BLACK;return true;}
?
接下來就來講解插入的節點的父節點為紅時進行調整的3種情況
注:接下來的講解當中假設我們把新增結點標識為c (cur),c的父親標識為p(parent),p的父親標識為g(grandfather),p的兄弟標識為u(uncle)。
1.情況1:只需變色
當插入的節點的為父親節點為紅時,而且父親的父親的另一個節點也是紅的,也就是c節點的叔叔節點u也為紅時。因為在插入新的節點c之前當前的樹一定是滿足紅黑樹的規則的,那么此時p節點的父親節點f一定是為黑,也就是c節點的祖父節點一定為黑。
?
例如以下示例:
新插入的節點為x,此時c、p、u的節點顏色情況就滿足以上描述的形式。那么要讓該節點變回滿足紅黑樹的規則,就只需要將p、u變黑;g變紅即可。這時調整完之后就可以使得樹當中每條路徑內的黑色節點的個數都相同。
以下是只進行變色情況的抽象表達,d/e/f代表每條路徑擁有hb個黑色結點的子樹,a/b代表每條路徑擁有hb-1個黑色結點的根為紅的子樹,hb>=0。和之前學習AVL樹一樣接下來來通過看看幾種只進行變色的情況。
那么接下來就開看看 hb==0 、hb==1、hb==2的情況?,其中當hb等于2時,這?組合情況上百億種,這些樣例是幫助我們理解,不論情況多少種,多么復雜,處理方式?樣的,變?再繼續往上處理即可,所以我們只需要看抽象圖即可。
通過以上的示例就可以看出在處理只需要變色的情況時,新出現的紅色節點可能是新插入的也可能是下部分的節點調整上來的,此時只需要一直進行調整直到對應的p為黑時就停止?
實現的代碼如下所示:
以下以p節點分別為g節點的左節點和右節點的兩種情況分別進行分析
?
bool Insert(const pair <K, V> kv)
{//當插入節點時的樹為空時就將新插入的節點置為樹的根節點,且顏色變為黑if (root == nullptr){root = new Node(kv);root->_col = BLACK;return true;}//尋找合適的插入位置Node* cur = root;Node* Parent = nullptr;while (cur){if (cur->_val.first < kv.first){Parent = cur;cur = cur->_right;}else if (cur->_val.first > kv.first){Parent = cur;cur = cur->_left;}else{return false;}}//創建新節點cur = new Node(kv);if (Parent->_val.first < kv.first){Parent->_right = cur;}else{Parent->_left = cur;}cur->_parent = Parent;cur->_col = RED;while (Parent && Parent->_col == RED){Node* grandfather = Parent->_parent;//父節點為祖父節點的左節點時// g// p u//cif (grandfather->_left == Parent){Node* uncle = grandfather->_right;//叔叔存在且為紅if (uncle && uncle->_col == RED){Parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;Parent = cur->_parent;}//其他情況……}//父節點為祖父節點的右節點時// g// u p// celse{Node* uncle = grandfather->_left;//叔叔存在且為紅if (uncle && uncle->_col == RED){Parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;Parent = cur->_parent;}//其他情況……}}//無論以上是否進行調整都將根結點的顏色置為黑root->_col = BLACK;return true;}
2.情況2:單旋+變色
以上情況1當中是叔叔存在且為紅的情況,那么如果叔叔不存在或者是不為紅又要怎么進行調整呢?
首先是c為紅,p為紅,g為?,u不存在或者u存在且為?,u不存在,則c?定是新增結點,u存在且為?,則c?定不是新增,c之前是??的,是在c的?樹中插?,符合情況1,變?將c從??變成紅?,更新上來的。
?
在此p是必須變為黑的,但是由于u為黑或者不存在,那么這就使得將p變為黑時p節點所在的路徑的黑色節點的個數就與其他的路徑不相同,那么這時就需要進行旋轉來解決問題。
在此也是以p節點分別為g節點的左節點和右節點的兩種情況分別進行分析
gp u
c
如果p是g的左,c是p的左,那么以g為旋轉點進行右單旋,再把p變?,g變紅即可。p變成課這顆樹新的根,這樣?樹??結點的數量不變,沒有連續的紅?結點了,且不需要往上更新,因為p的?親是黑色還是紅色或者空都不違反規則。
gu pc
如果p是g的右,c是p的右,那么以g為旋轉點進行左單旋,再把p變?,g變紅即可。p變成課這顆樹新的根,這樣?樹??結點的數量不變,沒有連續的紅?結點了,且不需要往上更新,因為p的?親是??還是紅?或者空都不違反規則。
3.情況3:雙旋+變色
?
以上我們分析了單旋的情,那么接下來就來分析雙旋的情況,當插入c之后紅黑樹的子樹結構如下所示時只使用單旋是無法實現調整之后的樹滿足紅黑樹的規則的,在此接下來要使用到雙旋才能調整至滿足要求。
c為紅,p為紅,g為黑,u不存在或者u存在且為黑,u不存在,則c?定是新增結點,u存在且為黑,則c?定不是新增,c之前是黑色的,是在c的子樹中插入,符合情況1,變?將c從黑色變成紅?,更新上來的。
在此p必須變黑,才能解決,連續紅色結點的問題,u不存在或者是??的,但是這?單純的變色?法解決問題,需要旋轉+變色。
gp uc
如果p是g的左,c是p的右,那么先以p為旋轉點進行左單旋,再以g為旋轉點進行右單旋,再把c變
黑,g變紅即可。c變成課這顆樹新的根,這樣子樹黑?結點的數量不變,沒有連續的紅色結點了,且不需要往上更新,因為c的?親是??還是紅?或者空都不違反規則。
?
gu pc
如果p是g的右,c是p的左,那么先以p為旋轉點進行右單旋,再以g為旋轉點進行左單旋,再把c變
?,g變紅即可。c變成課這顆樹新的根,這樣?樹黑色結點的數量不變,沒有連續的紅色結點了,且不需要往上更新,因為c的?親是黑色還是紅色或者空都不違反規則。
2.4 插入完整代碼
以上我們就進行插入節點的三種情況的分析,那么接下來就將以上的插入代碼補充完整
bool Insert(const pair <K, V> kv)
{//當插入節點時的樹為空時就將新插入的節點置為樹的根節點,且顏色變為黑if (root == nullptr){root = new Node(kv);root->_col = BLACK;return true;}//尋找合適的插入位置Node* cur = root;Node* Parent = nullptr;while (cur){if (cur->_val.first < kv.first){Parent = cur;cur = cur->_right;}else if (cur->_val.first > kv.first){Parent = cur;cur = cur->_left;}else{return false;}}//創建新節點cur = new Node(kv);if (Parent->_val.first < kv.first){Parent->_right = cur;}else{Parent->_left = cur;}cur->_parent = Parent;cur->_col = RED;while (Parent && Parent->_col == RED){Node* grandfather = Parent->_parent;//父節點為祖父節點的左節點時// g// p u//cif (grandfather->_left == Parent){Node* uncle = grandfather->_right;//叔叔存在且為紅if (uncle && uncle->_col == RED){Parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;Parent = cur->_parent;}//叔叔不存在或者為黑else{//當c為p的左節點時// g// p u//cif (Parent->_left == cur){RotateR(grandfather);grandfather->_col = RED;Parent->_col = BLACK;}//當c為p的右節點時// g// p u// celse{RotateL(Parent);RotateR(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}//父節點為祖父節點的右節點時// g// u p// celse{Node* uncle = grandfather->_left;//叔叔存在且為紅if (uncle && uncle->_col == RED){Parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;Parent = cur->_parent;}//叔叔不存在或者為黑else{//當c為p的右節點時// g// u p// cif (Parent->_right == cur){RotateL(grandfather);grandfather->_col = RED;Parent->_col = BLACK;}//當c為p的左節點時// g// u p// celse{RotateR(Parent);RotateL(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}}//無論以上是否進行調整都將根結點的顏色置為黑root->_col = BLACK;return true;}void RotateR(Node* Parent)
{Node* subL = Parent->_left;Node* subLR = subL->_right;if (subLR != nullptr){subLR->_parent = Parent;}Node* tmpNode = Parent->_parent;Parent->_left = subLR;Parent->_parent = subL;subL->_right = Parent;if (tmpNode != nullptr){if (tmpNode->_left == Parent){tmpNode->_left = subL;}else{tmpNode->_right = subL;}subL->_parent = tmpNode;}else{root = subL;subL->_parent = nullptr;}}void RotateL(Node* Parent)
{Node* subR = Parent->_right;Node* subRL = subR->_left;if (subRL != nullptr){subRL->_parent = Parent;}Node* tmpNode = Parent->_parent;Parent->_right = subRL;Parent->_parent = subR;subR->_left = Parent;if (tmpNode != nullptr){if (tmpNode->_left == Parent){tmpNode->_left = subR;}else{tmpNode->_right = subR;}subR->_parent = tmpNode;}else{root = subR;subR->_parent = nullptr;}}
2.4 紅黑樹查找
在此紅黑樹的查找實現和之前實現的二叉搜索樹和AVL樹類似,對你來說實現查找的代碼肯定是 so easy 的,在此就不再進行講解,直接奉上代碼:
Node* Find(const K& val)
{if (root == nullptr){return nullptr;}Node* cur = root;while (cur){if (cur->_val.first < val){cur = cur->_left;}else if (cur->_val.first > val){cur = cur->_right;}else{return cur;}}return nullptr;
}
2.5?紅黑樹刪除
在此紅黑樹的刪除較為復雜且不是很重要,在此就不進行講解。
?
2.6?紅黑樹驗證
以上我們實現了紅黑樹的插入以及查找,那么接下來就來實現驗證的代碼
首先來分析驗證的代碼該如何實現:
這力獲取最長路徑和最短路徑,檢查最長路徑不超過最短路徑的2倍是不可行的,因為就算滿足這個條件,紅黑樹也可能顏色不滿足規則,當前暫時沒出問題,后續繼續插入還是會出問題的。所以我們還是去檢查4點規則,滿足這4點規則,?定能保證最長路徑不超過最短路徑的2倍。
1. 規則1枚舉顏?類型,天然實現保證了顏色不是黑色就是紅色。
2. 規則2直接檢查根即可
3. 規則3前序遍歷檢查,遇到紅色結點查孩子不太方便,因為孩?有兩個,且不?定存在,反過來檢查父親的顏色就方便多了。
4. 規則4前序遍歷,遍歷過程中用形參記錄跟到當前結點的blackNum(黑色結點數量),前序遍歷遇到黑色結點就++blackNum,走到空就計算出了?條路徑的??結點數量。再任意?條路徑黑色結點數量作為參考值,依次比較即可。
實現代碼如下所示:
bool Check(Node* root, int blackNum, const int refNum)
{if (root == nullptr){// 前序遍歷?到空時,意味著?條路徑?完了//cout << blackNum << endl;if (refNum != blackNum){cout << "存在??結點的數量不相等的路徑" << endl;return false;}return true;}// 檢查孩?不太?便,因為孩?有兩個,且不?定存在,反過來檢查?親就?便多了if (root->_col == RED && root->_parent->_col == RED){cout << root->_val.first << "存在連續的紅?結點" << endl;return false;}if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);
}bool IsBalance()
{if (root == nullptr)return true;if (root->_col == RED)return false;// 參考值int refNum = 0;Node* cur = root;while (cur){if (cur->_col == BLACK){++refNum;}cur = cur->_left;}return Check(root, 0, refNum);
}
測試用例:
void TestAVLTree1()
{RBTree<int, int> t;// 常規的測試?例int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };// 特殊的帶有雙旋場景的測試?例//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.Insert({ e, e });}t.InOrder();//cout << t.IsBalanceTree() << endl;cout<<t.IsBalance();
}
運行以上代碼輸出如下所示:
?
這時只能是說明對以上示例的測試用例進行插入是沒問題的,要更嚴謹的驗證就需要有更多的測試用例,這時使用以上的代碼進行驗證?,并且測試進行插入的效率以及查找效率
void TestAVLTree2()
{const int N = 1000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin2 = clock();RBTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end2 = clock();cout << t.IsBalance() << endl;cout << "Insert:" << end2 - begin2 << endl;cout << "Height:" << t.Height() << endl;cout << "Size:" << t.Size() << endl;size_t begin1 = clock();// 確定在的值/*for (auto e : v){t.Find(e);}*/// 隨機值for (size_t i = 0; i < N; i++){t.Find((rand() + i));}size_t end1 = clock();cout << "Find:" << end1 - begin1 << endl;
}
將vs的調整為release模式之后,通過輸出的結果就可以看出我們實現的紅黑樹的插入代碼是沒問題的,并且實現的紅黑樹的插入效率以及查找效率都是很高的,效率基本和AVL樹不差上下。?
2.7 完整代碼
RBTree.c
#pragma once
#include<iostream>using namespace std;enum colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{pair<K, V> _val;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;colour _col;RBTreeNode(const pair<K, V>& val):_val(val), _left(nullptr), _right(nullptr), _parent(nullptr){}};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;public:bool Insert(const pair <K, V> kv){//當插入節點時的樹為空時就將新插入的節點置為樹的根節點,且顏色變為黑if (root == nullptr){root = new Node(kv);root->_col = BLACK;return true;}//尋找合適的插入位置Node* cur = root;Node* Parent = nullptr;while (cur){if (cur->_val.first < kv.first){Parent = cur;cur = cur->_right;}else if (cur->_val.first > kv.first){Parent = cur;cur = cur->_left;}else{return false;}}//創建新節點cur = new Node(kv);if (Parent->_val.first < kv.first){Parent->_right = cur;}else{Parent->_left = cur;}cur->_parent = Parent;cur->_col = RED;while (Parent && Parent->_col == RED){Node* grandfather = Parent->_parent;//父節點為祖父節點的左節點時// g// p u//cif (grandfather->_left == Parent){Node* uncle = grandfather->_right;//叔叔存在且為紅if (uncle && uncle->_col == RED){Parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;Parent = cur->_parent;}//叔叔不存在或者為黑else{//當c為p的左節點時// g// p u//cif (Parent->_left == cur){RotateR(grandfather);grandfather->_col = RED;Parent->_col = BLACK;}//當c為p的右節點時// g// p u// celse{RotateL(Parent);RotateR(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}//父節點為祖父節點的右節點時// g// u p// celse{Node* uncle = grandfather->_left;//叔叔存在且為紅if (uncle && uncle->_col == RED){Parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;Parent = cur->_parent;}//叔叔不存在或者為黑else{//當c為p的右節點時// g// u p// cif (Parent->_right == cur){RotateL(grandfather);grandfather->_col = RED;Parent->_col = BLACK;}//當c為p的左節點時// g// u p// celse{RotateR(Parent);RotateL(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break;}}}//無論以上是否進行調整都將根結點的顏色置為黑root->_col = BLACK;return true;}Node* Find(const K& val){if (root == nullptr){return nullptr;}Node* cur = root;while (cur){if (cur->_val.first < val){cur = cur->_left;}else if (cur->_val.first > val){cur = cur->_right;}else{return cur;}}return nullptr;}void InOrder(){_InOrder(root);cout << endl;}int Height(){return _Height(root);}int Size(){return _Size(root);}bool Check(Node* root, int blackNum, const int refNum){if (root == nullptr){// 前序遍歷?到空時,意味著?條路徑?完了//cout << blackNum << endl;if (refNum != blackNum){cout << "存在??結點的數量不相等的路徑" << endl;return false;}return true;}// 檢查孩?不太?便,因為孩?有兩個,且不?定存在,反過來檢查?親就?便多了if (root->_col == RED && root->_parent->_col == RED){cout << root->_val.first << "存在連續的紅?結點" << endl;return false;}if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}bool IsBalance(){if (root == nullptr)return true;if (root->_col == RED)return false;// 參考值int refNum = 0;Node* cur = root;while (cur){if (cur->_col == BLACK){++refNum;}cur = cur->_left;}return Check(root, 0, refNum);}private:Node* root = nullptr;void RotateR(Node* Parent){Node* subL = Parent->_left;Node* subLR = subL->_right;if (subLR != nullptr){subLR->_parent = Parent;}Node* tmpNode = Parent->_parent;Parent->_left = subLR;Parent->_parent = subL;subL->_right = Parent;if (tmpNode != nullptr){if (tmpNode->_left == Parent){tmpNode->_left = subL;}else{tmpNode->_right = subL;}subL->_parent = tmpNode;}else{root = subL;subL->_parent = nullptr;}}void RotateL(Node* Parent){Node* subR = Parent->_right;Node* subRL = subR->_left;if (subRL != nullptr){subRL->_parent = Parent;}Node* tmpNode = Parent->_parent;Parent->_right = subRL;Parent->_parent = subR;subR->_left = Parent;if (tmpNode != nullptr){if (tmpNode->_left == Parent){tmpNode->_left = subR;}else{tmpNode->_right = subR;}subR->_parent = tmpNode;}else{root = subR;subR->_parent = nullptr;}}void _InOrder(Node* cur){if (cur == nullptr){return;}_InOrder(cur->_left);cout << cur->_val.first << ":"<<cur->_val.second<<endl;_InOrder(cur->_right);}int _Height(Node* cur){if (cur == nullptr){return 0;}int Left = _Height(cur->_left);int Right = _Height(cur->_right);return Left > Right ? Left + 1 : Right + 1;}int _Size(Node* cur){if (cur == nullptr){return 0;}return 1 + _Size(cur->_left) + _Size(cur->_right);}};
test.cpp
#include<iostream>
#include<vector>
#include"BRTree.h"using namespace std;void TestAVLTree1()
{RBTree<int, int> t;// 常規的測試?例int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };// 特殊的帶有雙旋場景的測試?例//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.Insert({ e, e });}t.InOrder();//cout << t.IsBalanceTree() << endl;cout<<t.IsBalance();
}void TestAVLTree2()
{const int N = 1000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin2 = clock();RBTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end2 = clock();cout << t.IsBalance() << endl;cout << "Insert:" << end2 - begin2 << endl;cout << "Height:" << t.Height() << endl;cout << "Size:" << t.Size() << endl;size_t begin1 = clock();// 確定在的值/*for (auto e : v){t.Find(e);}*/// 隨機值for (size_t i = 0; i < N; i++){t.Find((rand() + i));}size_t end1 = clock();cout << "Find:" << end1 - begin1 << endl;
}int main()
{//TestAVLTree1();TestAVLTree2();return 0;
}
以上就是本篇的全部內容了,在實現了紅黑樹之后接下來我們就可以基于紅黑樹來自己實現封裝set和map,未完待續……