数据结构拾遗(1) --红黑树的设计与实现(上)
紅黑樹是一種自平衡的二叉查找樹,是在計算機科學(xué)中用到的一種數(shù)據(jù)結(jié)構(gòu),典型的用途是實現(xiàn)關(guān)聯(lián)數(shù)組(C++?STL?中的map/set)。它是在1972年由Rudolf?Bayer發(fā)明的,他稱之為"對稱二叉B樹",它現(xiàn)代的名字是在?Leo?J.?Guibas?和?Robert?Sedgewick?于1978年寫的一篇論文中獲得的。紅黑樹雖然很復(fù)雜,但它的操作有著良好的最壞情況運行時間,并且在實踐中是高效的:?它可以在O(log?n)時間內(nèi)做查找,插入和刪除,這里的n?是樹中元素的數(shù)目(來源:百度百科)。
?
算法導(dǎo)論對R-B?Tree的介紹:
紅黑樹,一種二叉查找樹,但在每個結(jié)點上增加一個存儲位表示結(jié)點的顏色,可以是Red或Black。通過對任何一條從根到葉子的路徑上各個結(jié)點著色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出兩倍,因而是接近平衡的。
?
由于紅黑樹也是一顆二叉查找樹,?因此紅黑樹也滿足二叉查找樹的一般性質(zhì)(關(guān)于二叉查找樹的一般性質(zhì)請參考博客:http://blog.csdn.net/zjf280441589/article/details/42611161)
但由于普通的二叉查找樹不具備自動平衡的功能,?因此普通的二叉查找樹樹很有可能會退化成為一條鏈表,?若二叉樹退化成了一棵具有n個結(jié)點的線性鏈后,則插入/刪除/查找操作最壞情況運行時間就變?yōu)榱薕(n)。
因此就有了紅黑樹,?他的最終查找、插入、刪除的時間復(fù)雜度最壞情況下依然為O(logn),?而紅黑樹之所以這么牛的原因就是它在二叉查找樹的基礎(chǔ)上增加了著色和相關(guān)的性質(zhì),?這就是紅黑規(guī)則:
紅黑規(guī)則
? ?1.每一個節(jié)點不是紅色的就是黑色的;
? ?2.根總是黑色的;
? ?3.如果節(jié)點是紅色的,?則它的子節(jié)點必須是黑色的;
? ?4.從根到葉節(jié)點的每條路徑,?必須包含相同數(shù)目的黑色節(jié)點(黑高度相同);
? ?5.每個葉結(jié)點(葉結(jié)點即指樹尾端NIL指針或NULL結(jié)點)是黑的;
? 根據(jù)規(guī)則4,?新增節(jié)點必須為紅;?
? 根據(jù)規(guī)則3,?新增節(jié)點之父節(jié)點必須為黑. ?
? 當(dāng)新節(jié)點根據(jù)二叉樹的規(guī)則到達(dá)插入點,?卻未能符合上述紅黑規(guī)則時,?就必須調(diào)整顏色并旋轉(zhuǎn)樹形;
?
紅黑結(jié)點
template <typename Type> class RedBlackNode {friend RedBlackTree<Type>; private:RedBlackNode(const Type &_element = Type(),RedBlackNode *_left = NULL,RedBlackNode *_right = NULL,int _color = RedBlackTree<Type>::BLACK): element(_element), left(_left), right(_right), color(_color) {}private: //為了在紅黑樹尚未完成的時候進行測試 //故將之作為public, 待紅黑樹完成之時, //就是將其改為private之時; public:Type element; //節(jié)點值RedBlackNode *left;RedBlackNode *right;//紅黑樹的顏色int color; };紅黑樹
template <typename Type> class RedBlackTree { public://為結(jié)點定義別名typedef RedBlackNode<Type> Node;enum {RED, BLACK};public://此時紅黑樹所支持的操作尚不完善,//而且函數(shù)功能(如析構(gòu)函數(shù), insert操作)//實現(xiàn)的也并不理想,//但隨著紅黑樹代碼的不斷演進,//這些代碼會不斷的完善RedBlackTree(const Type &negInf);~RedBlackTree();void insert(const Type &data);private: //為了在紅黑樹尚未完成的時候進行測試 //故將之作為public, 待紅黑樹完成之時, //就是將其改為private之時; public://指向紅黑樹的頭(偽根節(jié)點)//RedBlackNode<Type> *header;Node *header;//空節(jié)點Node *nullNode;//在插入過程中需要的指針Node *current; //當(dāng)前節(jié)點Node *parent; //父節(jié)點Node *grand; //祖父節(jié)點(爺爺)Node *great; //曾祖父節(jié)點(爺爺?shù)陌职?/* 單旋轉(zhuǎn) *///帶著左孩子旋轉(zhuǎn): 向右旋轉(zhuǎn)void rotateWithLeftChild(Node *& k2) const;//帶著有孩子旋轉(zhuǎn): 向左旋轉(zhuǎn)void rotateWithRightChild(Node *& k1) const;/* 雙旋轉(zhuǎn) *///向右轉(zhuǎn)void doubleRotateWithLeftChild(Node *& k3) const;//向左轉(zhuǎn)void doubleRotateWithRightChild(Node *& k1) const; };構(gòu)造與析構(gòu)
//構(gòu)造函數(shù) template <typename Type> RedBlackTree<Type>::RedBlackTree(const Type &negInf) {nullNode = new Node();header = new Node(negInf, nullNode, nullNode); } //這一版的析構(gòu)函數(shù)實現(xiàn)并不完善,在后續(xù)的版本我們會繼續(xù)完善該析構(gòu)函數(shù) template <typename Type> RedBlackTree<Type>::~RedBlackTree() {delete nullNode;delete header; }一個二叉查找樹的insert
//這時的insert其實就是一個普通的 //二叉查找樹的insert, 完全沒要照顧到 //二叉樹的平衡, 以及紅黑規(guī)則的實現(xiàn) template <typename Type> void RedBlackTree<Type>::insert(const Type &data) {great = grand = parent = current = header;//在此處令nullNode成為data, 以作哨兵nullNode->element = data;while (current->element != data){//讓祖父成為曾祖父, 父親成為祖父, 自己成為父親//每個人都長了一輩great = grand;grand = parent;parent = current;current = (data < current->element) ? current->left: current->right;}//如果樹中包含相同的元素if (current != nullNode)throw DuplicateItemException();current = new Node(data, nullNode, nullNode);if (data < parent->element)parent->left = current;elseparent->right = current;//在后續(xù)的版本上,需要加上自動平衡(即實現(xiàn)紅黑規(guī)則) -> 紅黑樹 }單旋轉(zhuǎn)
注意:內(nèi)側(cè)孫子節(jié)點橫向移動(注意下圖結(jié)點37);
?
左(單)旋:
?
當(dāng)在某個結(jié)點k1上,做左旋操作時,我們假設(shè)它的右孩子k2不是NIL[T](k1可以是任何不是NIL[T]的左孩子結(jié)點);?左旋以k1到k2之間的鏈為“支軸”進行,它使k2成為該子樹新的根,而k2的左孩子B則成為k1的右孩子。?
//實現(xiàn) //向左轉(zhuǎn) template <typename Type> void RedBlackTree<Type>::rotateWithRightChild(Node *& k1) const {Node *k2 = k1->right;//結(jié)點B橫向移動k1->right = k2->left;//令k2提領(lǐng)k1k2->left = k1;//令k2為根(使k2替代k1的位置)k1 = k2; }右(單)旋:
?
? ? 過程與左旋類似;
//實現(xiàn) //向右轉(zhuǎn) template <typename Type> void RedBlackTree<Type>::rotateWithLeftChild(Node *& k2) const {//首先將B橫向移動Node *k1 = k2->left;k2->left = k1->right;//令k1提領(lǐng)k2k1->right = k2;//令k1為根(使k1替代k2的位置)k2 = k1; }測試(在完成單旋轉(zhuǎn)之后):
?
? ? (構(gòu)造一顆二叉查找樹樹如圖所示,?左邊為尚未旋轉(zhuǎn)之前,?右為旋轉(zhuǎn)之后)
//測試代碼如下: int main() {//用NEG_INF來代表負(fù)無窮大const int NEG_INF = -999999;RedBlackTree<int> tree(NEG_INF);//單旋轉(zhuǎn)時候的測試數(shù)據(jù)tree.insert(30);tree.insert(15);tree.insert(70);tree.insert(20);cout << tree.header->right->element << endl;cout << tree.header->right->left->element << endl;cout << tree.header->right->right->element << endl;cout << tree.header->right->left->right->element << endl; //20//向右旋轉(zhuǎn)cout << "向右旋轉(zhuǎn)" << endl;tree.rotateWithLeftChild(tree.header->right);cout << tree.header->right->element << endl; //15cout << tree.header->right->right->element << endl; //30cout << tree.header->right->right->left->element << endl; //20cout << tree.header->right->right->right->element << endl; //70//然后再向左轉(zhuǎn)(又轉(zhuǎn)了回來)cout << "向左旋轉(zhuǎn)" << endl;tree.rotateWithRightChild(tree.header->right);cout << tree.header->right->element << endl;cout << tree.header->right->left->element << endl;cout << tree.header->right->right->element << endl;cout << tree.header->right->left->right->element << endl; //20 }雙旋轉(zhuǎn)
單旋轉(zhuǎn)有時會出現(xiàn)一個問題(如下圖所示):
? ? (如果內(nèi)側(cè)子孫節(jié)點[k1]過深,?則將其單向移動是不會解決問題的)
?
于是就有了雙旋轉(zhuǎn)
向右雙旋轉(zhuǎn):
? ?1.首先以k1為軸,?k1與k2向左旋轉(zhuǎn);
? ?2.然后以k3為軸,?k3與旋轉(zhuǎn)之后的k1向右旋轉(zhuǎn);
//實現(xiàn) //向右雙旋轉(zhuǎn) template <typename Type> void RedBlackTree<Type>::doubleRotateWithLeftChild(Node *& k3) const {//首先將其左兒子(k1)向左單旋轉(zhuǎn)rotateWithRightChild(k3->left);//然后將自己(k3)向右單旋轉(zhuǎn)rotateWithLeftChild(k3); }向左雙旋轉(zhuǎn):
? ?1.首先以k3為軸,?k2與k3向右旋轉(zhuǎn);
? ?2.然后以k1為軸,?k1與旋轉(zhuǎn)之后的k2向左旋轉(zhuǎn);
//實現(xiàn) //向左雙旋轉(zhuǎn) template <typename Type> void RedBlackTree<Type>::doubleRotateWithRightChild(Node *& k1) const {//首先將其右兒子(k2)向右單旋轉(zhuǎn)rotateWithLeftChild(k1->right);//然后將自己(k1)向左單旋轉(zhuǎn)rotateWithRightChild(k1); } //注:其實紅黑樹中并沒有用到雙旋轉(zhuǎn), 而是自己實現(xiàn)了一個rotate操作, //在此只為了學(xué)習(xí)雙旋轉(zhuǎn)的理論;測試(在完成雙旋轉(zhuǎn)之后):
(構(gòu)造一顆二叉查找樹樹如圖所示,?左邊為尚未旋轉(zhuǎn)之前,?右為旋轉(zhuǎn)之后,?以8為軸進行雙旋轉(zhuǎn))
int main() {//用NEG_INF來代表負(fù)無窮大const int NEG_INF = -999999;RedBlackTree<int> tree(NEG_INF);//雙旋轉(zhuǎn)時的測試數(shù)據(jù)tree.insert(12);tree.insert(16);tree.insert(8);tree.insert(10);tree.insert(4);tree.insert(14);tree.insert(2);tree.insert(6);tree.insert(5);cout << tree.header->right->element << endl; //12cout << tree.header->right->left->element << endl; //8cout << tree.header->right->right->element << endl; //16cout << tree.header->right->left->left->element << endl; //4cout << tree.header->right->left->right->element << endl; //10cout << tree.header->right->right->left->element << endl; //14 // cout << tree.header->right->left->right->left->element << endl; //5曾經(jīng)做過哨兵 // cout << tree.header->right->left->right->right->element << endl; //5cout << tree.header->right->left->left->left->element << endl; //2cout << tree.header->right->left->left->right->element << endl; //6cout << tree.header->right->left->left->right->left->element << endl; //5cout << "\n向右雙旋轉(zhuǎn)" << endl;//以8為基準(zhǔn)向右雙旋轉(zhuǎn)tree.doubleRotateWithLeftChild(tree.header->right->left);cout << tree.header->right->element << endl; //12cout << tree.header->right->left->element << endl; //6cout << tree.header->right->right->element << endl; //16cout << tree.header->right->left->left->element << endl; //4cout << tree.header->right->left->right->element << endl; //8cout << tree.header->right->right->left->element << endl; //14cout << tree.header->right->left->left->left->element << endl; //2cout << tree.header->right->left->left->right->element << endl; //5cout << tree.header->right->left->right->right->element << endl; //10 }
實現(xiàn)紅黑樹用到的異常
class DSException { public:DSException(const string &_message = string()): message(_message) {}~DSException() {}virtual string what() const{return message;}virtual string toString() const{return "Exception: " + what();}private:std::string message; };class DuplicateItemException : public DSException { public:DuplicateItemException(const string &_msg = string()): DSException(_msg) {} };總結(jié)
以上是生活随笔為你收集整理的数据结构拾遗(1) --红黑树的设计与实现(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 常用MySQ调优策略及相关分享:学习随记
- 下一篇: JEUS6启动步骤