目錄
1.紅黑樹的概念
2.紅黑樹的性質
3.紅黑樹節點的定義
?4.紅黑樹的插入操作
?5.數據測試
1.紅黑樹的概念
????????紅黑樹,是一種二叉搜索樹,但在每個結點上增加一個存儲位表示結點的顏色,可以是Red或Black。 通過對任何一條從根到葉子的路徑上各個結點著色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出倆倍,因而是接近平衡的
2.紅黑樹的性質
1. 每個結點不是紅色就是黑色
2. 根節點是黑色的
3. 如果一個節點是紅色的,則它的兩個孩子結點是黑色的
4. 對于每個結點,從該結點到其所有后代葉結點的簡單路徑上,均 包含相同數目的黑色結點
5. 每個葉子結點都是黑色的(此處的葉子結點指的是空結點)
思考:為什么滿足上面的性質,紅黑樹就能保證:其最長路徑中節點個數不會超過最短路徑節點個數的兩倍?
- 最短路徑:由于性質4規定從任意節點到葉子節點的所有路徑都包含相同數量的黑色節點,因此由純黑色節點組成的路徑將是最短路徑。這是因為黑色節點是每條路徑上必須包含的,而且沒有其他顏色的節點可以“縮短”這條路徑。
- 最長路徑:最長路徑將是由紅色和黑色節點交替組成的路徑。由于性質3禁止了連續紅色節點的出現,因此最長路徑將是黑色節點與紅色節點交替出現的情況。由于每條路徑上的黑色節點數量相同(性質4),紅色節點的插入不會增加路徑上黑色節點的數量,而是增加了路徑的總長度。
- 路徑長度比較:在最長路徑中,每兩個黑色節點之間可能插入一個紅色節點,這使得最長路徑的長度大致為最短路徑(純黑色節點)的兩倍。這是因為在保持黑色節點數量相同的情況下,紅色節點的插入只是在黑色節點之間增加了額外的節點,而這些額外的紅色節點最多只能使路徑長度加倍。
3.紅黑樹節點的定義
enum Colour {RED,BLACK };template<class K, class V> struct RBTreeNode {RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){} };template<class K, class V> class RBTree {typedef RBTreeNode<K, V> Node;private:Node* _root = nullptr; };
- 成員變量:
_left
、_right
?和?_parent
?分別指向節點的左子節點、右子節點和父節點。_kv
?是一個?pair<K, V>
,存儲節點的鍵和值。_col
?表示節點的顏色,可以是?RED
?或?BLACK
。- 構造函數:
接收一個?pair<K, V>
?作為參數,初始化節點的鍵值對,并將節點的顏色初始化為?RED
。其他指針成員初始化為?nullptr
。
?4.紅黑樹的插入操作
????????我們在進行插入操作時,新節點默認是紅色。紅色節點的插入可能導致紅黑樹的性質被破壞,但通過將新節點設為紅色,我們可以更容易地通過顏色變換和旋轉來恢復平衡。具體來說,紅色節點的插入只會影響局部區域的平衡,而黑色節點的插入則可能影響整棵樹的平衡。
因為新節點的默認顏色是紅色,因此:如果其雙親節點的顏色是黑色,沒有違反紅黑樹任何
性質,則不需要調整;但當新插入節點的雙親節點顏色為紅色時,就違反了性質三不能有連
在一起的紅色節點,此時需要對紅黑樹分情況來討論:
約定:cur為當前節點,p為父節點,g為祖父節點,u為叔叔節點
情況一: cur為紅,p為紅,g為黑,u存在且為紅情況二: cur為紅,p為紅,g為黑,u不存在/u存在且為黑
p為g的左孩子,cur為p的左孩子,則進行右單旋轉;相反,
p為g的右孩子,cur為p的右孩子,則進行左單旋轉
p、g變色--p變黑,g變紅
?情況三: cur為紅,p為紅,g為黑,u不存在/u存在且為黑(雙旋)
p為g的左孩子,cur為p的右孩子,則針對p做左單旋轉;相反,
p為g的右孩子,cur為p的左孩子,則針對p做右單旋轉
則轉換成了情況2
針對每種情況進行相應的處理即可。據以上情況寫代碼:
}cur = new Node(kv);cur->_col = RED; // 新增節點給紅色if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// parent的顏色是黑色也結束while (parent && parent->_col == RED){// 關鍵看叔叔Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;// 叔叔存在且為紅,-》變色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 繼續往上處理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且為黑{if (cur == parent->_left){// g // p u// c RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g // p u// c RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;// 叔叔存在且為紅,-》變色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 繼續往上處理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且為黑{// 情況二:叔叔不存在或者存在且為黑// 旋轉+變色// g// u p// cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}
代碼解釋:
- 初始化新節點:
- 創建一個新的節點
cur
,并為其分配鍵值對kv
。- 將新節點的顏色設置為紅色(RED),因為在紅黑樹的規則中,新插入的節點默認為紅色。
- 根據鍵值對的大小,將新節點插入到其父節點的左子樹或右子樹。
- 設置新節點的父節點。
- 調整紅黑樹以保持其性質:
- 如果父節點是黑色的,那么不需要進行任何調整,因為插入紅色節點不會違反紅黑樹的性質。
- 如果父節點是紅色的,那么需要進行調整以恢復紅黑樹的性質。這是通過一個循環來完成的,該循環會持續進行,直到父節點不再是紅色或者已經進行了必要的調整。
- 調整過程:
- 首先,根據父節點是其祖父節點的左子節點還是右子節點,分為兩種情況處理。
- 在每種情況下,都會考慮叔叔節點的顏色和存在性。
- 如果叔叔節點是紅色的,那么可以通過重新著色父節點、叔叔節點和祖父節點來恢復紅黑樹的性質,然后將當前節點移動到祖父節點,并更新父節點為祖父節點的父節點,繼續向上調整。
- 如果叔叔節點是黑色的或者不存在,那么需要進行旋轉操作來恢復紅黑樹的性質。根據當前節點是父節點的左子節點還是右子節點,進行相應的旋轉操作,并重新著色節點。
- 結束調整:
- 一旦退出調整循環,將根節點著色為黑色,以確保紅黑樹的根節點總是黑色的性質。
以下是旋轉邏輯的代碼示例:
void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (ppNode->_right == parent){ppNode->_right = subR;}else{ppNode->_left = subR;}subR->_parent = ppNode;}}
?5.數據測試
????????為了更好的測試數據,我們需要寫一個檢查一棵紅黑樹是否滿足紅黑樹的性質,以確保紅黑樹的正確性:
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->_kv.first << "存在連續的紅色節點" << endl;return false;}if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}
代碼解釋:
空節點檢查:
- 如果
root
是空指針(即已經到達了一個葉節點),函數會檢查當前路徑上的黑色節點數量blackNum
是否與參考數量refNum
相等。- 如果不相等,會輸出錯誤信息,并返回
false
,表示檢查失敗。- 如果相等,則返回
true
,表示這條路徑滿足紅黑樹的性質。連續紅色節點檢查:
- 如果當前節點的顏色是紅色,并且其父節點的顏色也是紅色,那么會輸出錯誤信息,并返回
false
。因為紅黑樹的一個關鍵性質是不允許有兩個連續的紅色節點。黑色節點計數:
- 如果當前節點的顏色是黑色,
blackNum
會遞增,以記錄從根節點到當前節點的路徑上黑色節點的數量。遞歸檢查:
- 函數會遞歸地對左子樹和右子樹進行相同的檢查。
- 如果左右子樹都滿足紅黑樹的性質(即遞歸調用都返回
true
),則整個子樹滿足性質,函數返回true
。- 如果有任何一個子樹不滿足性質,函數返回
false
。下面是紅黑樹的完整代碼:
enum Colour {RED,BLACK };template<class K, class V> struct RBTreeNode {RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){} };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* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_col = RED; // 新增節點給紅色if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// parent的顏色是黑色也結束while (parent && parent->_col == RED){// 關鍵看叔叔Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;// 叔叔存在且為紅,-》變色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 繼續往上處理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且為黑{if (cur == parent->_left){// g // p u// c RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g // p u// c RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;// 叔叔存在且為紅,-》變色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 繼續往上處理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且為黑{// 情況二:叔叔不存在或者存在且為黑// 旋轉+變色// g// u p// cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (ppNode->_right == parent){ppNode->_right = subR;}else{ppNode->_left = subR;}subR->_parent = ppNode;}}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){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: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->_kv.first << "存在連續的紅色節點" << endl;return false;}if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}private:Node* _root = nullptr; };
數據測試:
void TestRBTree1() {int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,8, 3, 1, 10, 6, 4, 7, 14, 13 };RBTree<int, int> t1;for (auto e : a){if (e == 10){int i = 0;}t1.Insert({ e,e });cout << "Insert:" << e << "->" << t1.IsBalance() << endl;}t1.InOrder();cout << t1.IsBalance() << endl; }
符合預期,代碼正確。