C++數據結構之紅黑樹的實現
一、什麼是紅黑樹
紅黑樹在表意上就是一棵每個節點帶有顏色的二叉搜索樹,並通過對節點顏色的控制,使該二叉搜索樹達到盡量平衡的狀態。所謂盡量平衡的狀態就是:紅黑樹確保沒有一條路徑比其他路徑長兩倍。
和AVL樹不同的是,AVL樹是一棵平衡樹,而紅黑樹可能平衡也可能不平衡(因為是盡量平衡的狀態)
二、紅黑樹的約定
要實現一棵紅黑樹,即要紅黑樹確保沒有一條路徑比其他路徑長兩倍。通過對節點顏色的約定來實現這一目標。
1.根節點是黑色的。
2.如果一個節點是紅色的,則它的兩個孩子都是黑色的。
3.對於每個節點,從該節點到其所有後代節點的簡單路徑上,均包含相同數量的黑色節點。
實現瞭這三條顏色規則的二叉搜索樹,即也實現瞭沒有一條路徑比其他路徑長兩倍,即實現瞭一棵紅黑樹。
三、紅黑樹vsAVL
1、調整平衡的實現機制不同
紅黑樹根據節點顏色(同一父節點出發到葉子節點,所有路徑上的黑色節點數目一樣),一些約定和旋轉實現。
AVL根據樹的平衡因子(所有節點的左右子樹高度差的絕對值不超過1)和旋轉決定。
2、紅黑樹的插入效率更高
紅黑樹是用非嚴格的平衡來換取增刪節點時候旋轉次數的降低,任何不平衡都會在三次旋轉之內解決,紅黑樹並不追求“完全平衡”,它隻要求部分地達到平衡要求,降低瞭對旋轉的要求,從而提高瞭性能
而AVL是嚴格平衡樹(高度平衡的二叉搜索樹),因此在增加或者刪除節點的時候,根據不同情況,旋轉的次數比紅黑樹要多。所以紅黑樹的插入效率更高
3、AVL查找效率高
如果你的應用中,查詢的次數遠遠大於插入和刪除,那麼選擇AVL樹,如果查詢和插入刪除次數幾乎差不多,應選擇紅黑樹。即,有時僅為瞭排序(建立-遍歷-刪除),不查找或查找次數很少,R-B樹合算一些。
四、紅黑樹的實現
實現一棵紅黑樹,本質是實現它的插入函數,使插入函數可以實現紅黑樹的顏色約定,它的實現分為兩步,即先找到插入的位置,再控制平衡。插入函數返回值設計為bool,插入成功返回true,失敗返回false。控制平衡時,需要關註四個節點,即新插入的節點,它的父節點,它的叔叔節點,它的祖父節點。
1.找到插入的位置
當為第一個節點的時候,顏色設為黑,直接插入。
當插入的不是第一個節點時,顏色設為紅,需要根據二叉搜索樹的性質找到插入位置。並實現三叉鏈。
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; cur->_parent = parent; } else { parent->_left = cur; cur->_parent = parent; }
2.控制平衡
(1)當父節點為黑
當父節點為黑的時候,由於插入的子節點的顏色為紅,對三個約定沒有任何影響,因此不需要調整平衡。
(2)判斷父節點在祖父節點的位置
通過判斷父節點在祖父節點的位置,來定義叔叔節點的位置,以及之後的旋轉方向的判斷。
while (parent && parent->_col == Red) { Node* grandfather = parent->_parent; if (parent == grandfather->_left) { Node* uncle = grandfather->_right; //三種情況的處理 } else { Node* uncle = grandfather->_left; //三種情況的處理 }
首先需要使用大循環,這個循環是為情況1而準備的,情況2和3直接跳出循環即可,因為隻有情況1對上層循環有影響。
下面我們以父節點在祖父節點的左側為例,右側同理。
(3)叔叔節點存在且為紅
解決方案:將父節點和叔叔節點設為黑,將祖父節點設為紅。然後將祖父節點作為新節點繼續向上平衡。如果祖父節點是根節點,那麼需要再將其置為黑。
註意,這種情況和其他情況不同的是,需要將祖父節點作為新插入的節點繼續向上遍歷,這說明需要一個循環。而其他類型的情況直接break跳出這個循環即可。
//第一種情況 if (uncle && uncle->_col == Red) { parent->_col = uncle->_col = Black; grandfather->_col = Red; cur = grandfather; parent = cur->_parent; }
這種情況隻需要控制顏色即可,但是要繼續向上循環。
(4)父節點為紅,叔叔不存在或存在且為黑,新插入的節點在父節點左側
解決方案:對祖父節點右旋操作,並將祖父節點置為紅,父節點置為黑。
關於旋轉的細節,我在AVL樹中有詳細的解釋:C++手撕AVL樹
//第二種情況,右單旋 if (cur == parent->_left) { RotateR(grandfather); parent->_col = Black; grandfather->_col = Red; }
(5)父節點為紅,叔叔不存在或存在且為黑,新插入的節點在父節點右側
解決方案:進行雙旋,即對父節點進行左單旋,祖父節點進行右單旋。將子節點置為黑,將祖父節點置為紅。
else { RotateL(parent); RotateR(grandfather); cur->_col = Black; grandfather->_col = Red; }
(6)最後置黑
每一次插入都對根節點置為黑操作,因為第一種情況可能導致根節點不是黑。同時對根節點置黑也並不違反三條規定。
3.測試代碼
當我們處理完父節點在祖父節點的左側後,處理父節點在祖父節點的右側。
全部處理之後,我們的插入代碼就完成瞭,接下來要對整個樹進行測試,即對三個約定來進行測試:
1.當根節點為紅時,返回false。
2.判斷一個節點和其父節點的顏色是否都為紅,若都為紅返回false。
3.以最左的一條路徑上的根節點數量為基準,通過遞歸遍歷每一條路徑上的根節點的數量,當每條路徑遍歷節點到空時,將兩者進行比較,如果最終結果不相等則返回false。
bool IsBalance() { if (_root && _root->_col == Red) { cout << "根節點不是黑色的" << endl; return false; } int banchmark = 0; //以最右邊一條路徑為基準 Node* left = _root; while (left) { if (left->_col == Black) { ++banchmark; } left = left->_left; } int blackNum = 0; return _IsBalance(_root, banchmark, blackNum); } bool _IsBalance(Node* root, int banchmark, int blackNum) { if (root == nullptr) { if (banchmark != blackNum) { cout << "黑色根節點數目不相等" << endl; return false; } return true; } if (root->_col == Red && root->_parent->_col == Red) { cout << "出現連續的紅色節點" << endl; return false; } if (root->_col == Black) { ++blackNum; } return _IsBalance(root->_left, banchmark, blackNum) && _IsBalance(root->_right, banchmark, blackNum); }
五、完整代碼
1.test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"RBtree.h" #include<vector> int main() { RBTree<int, int> t; vector<int> v; srand(time(0)); int N = 100000; int count = 0; for (int i = 0; i < N; i++) { v.push_back(rand()); } for (auto e : v) { pair<int,int> kv(e, e); t.insert(kv); if (t.IsBalance()) { cout << "insert" << e << endl; count++; cout << count << endl; } else { cout << e << "插入失敗" << endl; cout << "不是平衡的" << endl; break; } } }
2.RBTree.h
#pragma once #include<iostream> #include<assert.h> using namespace std; enum Color { 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; Color _col; RBTreeNode(pair<K, V>& kv) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _col(Red) , _kv(kv) {} }; template<class K,class V> struct RBTree { typedef RBTreeNode<K, V> Node; private: Node* _root; public: RBTree() :_root(nullptr) {} bool IsBalance() { if (_root && _root->_col == Red) { cout << "根節點不是黑色的" << endl; return false; } int banchmark = 0; //以最右邊一條路徑為基準 Node* left = _root; while (left) { if (left->_col == Black) { ++banchmark; } left = left->_left; } int blackNum = 0; return _IsBalance(_root, banchmark, blackNum); } bool _IsBalance(Node* root, int banchmark, int blackNum) { if (root == nullptr) { if (banchmark != blackNum) { cout << "黑色根節點數目不相等" << endl; return false; } return true; } if (root->_col == Red && root->_parent->_col == Red) { cout << "出現連續的紅色節點" << endl; return false; } if (root->_col == Black) { ++blackNum; } return _IsBalance(root->_left, banchmark, blackNum) && _IsBalance(root->_right, banchmark, blackNum); } //右單旋 void RotateR(Node* parent) { Node* cur = parent->_left; Node* curL = cur->_left; Node* curR = cur->_right; Node* parentParent = parent->_parent; parent->_left = curR; if (curR) curR->_parent = parent; cur->_right = parent; parent->_parent = cur; if (parent == _root) { _root = cur; _root->_parent = nullptr; } else { if (parentParent->_left == parent) { parentParent->_left = cur; cur->_parent = parentParent; } else if (parentParent->_right == parent) { parentParent->_right = cur; cur->_parent = parentParent; } else { assert(false); } } } //左單旋 void RotateL(Node* parent) { Node* cur = parent->_right; Node* curL = cur->_left; Node* parentParent = parent->_parent; parent->_right = curL; if (curL) curL->_parent = parent; cur->_left = parent; parent->_parent = cur; if (parent == _root) { _root = cur; _root->_parent = nullptr; } else { if (parentParent->_left == parent) { parentParent->_left = cur; cur->_parent = parentParent; } else if (parentParent->_right == parent) { parentParent->_right = cur; cur->_parent = parentParent; } else { assert(false); } } } bool insert(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; cur->_parent = parent; } else { parent->_left = cur; cur->_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) { RotateR(grandfather); parent->_col = Black; grandfather->_col = Red; } //第三種情況,左右雙旋 else { RotateL(parent); RotateR(grandfather); cur->_col = Black; grandfather->_col = Red; } break; } _root->_col = Black; } else { Node* uncle = grandfather->_left; //第一種情況 if (uncle && uncle->_col == Red) { parent->_col = uncle->_col = Black; grandfather->_col = Red; cur = grandfather; parent = cur->_parent; } else { //第二種情況,左單旋 if (cur == parent->_right) { RotateL(grandfather); parent->_col = Black; grandfather->_col = Red; } //第三種情況,右左雙旋 else { RotateR(parent); RotateL(grandfather); cur->_col = Black; grandfather->_col = Red; } break; } _root->_col = Black; } } } };
到此這篇關於C++數據結構之紅黑樹的實現的文章就介紹到這瞭,更多相關C++紅黑樹內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- C++ STL容器詳解之紅黑樹部分模擬實現
- C++數據結構紅黑樹全面分析
- C++AVL樹4種旋轉詳講(左單旋、右單旋、左右雙旋、右左雙旋)
- C++ AVL樹插入新節點後的四種調整情況梳理介紹
- C++數據結構之搜索二叉樹的實現