紅黑樹是一種自平衡的二叉查找樹,是在計算機科學中用到的一種數據結構,典型的用途是實現關聯數組(C++ STL 中的map/set)。它是在1972年由Rudolf Bayer發明的,他稱之爲"對稱二叉B樹",它現代的名字是在 Leo J. Guibas 和 Robert Sedgewick 於1978年寫的一篇論文中獲得的。紅黑樹雖然很複雜,但它的操作有着良好的最壞情況運行時間,並且在實踐中是高效的: 它可以在O(log n)時間內做查找,插入和刪除,這裏的n 是樹中元素的數目(來源:百度百科)。
算法導論對R-B Tree的介紹:
紅黑樹,一種二叉查找樹,但在每個結點上增加一個存儲位表示結點的顏色,可以是Red或Black。通過對任何一條從根到葉子的路徑上各個結點着色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出兩倍,因而是接近平衡的。
由於紅黑樹也是一顆二叉查找樹, 因此紅黑樹也滿足二叉查找樹的一般性質(關於二叉查找樹的一般性質請參考博客:http://blog.csdn.net/zjf280441589/article/details/42611161)
但由於普通的二叉查找樹不具備自動平衡的功能, 因此普通的二叉查找樹樹很有可能會退化成爲一條鏈表, 若二叉樹退化成了一棵具有n個結點的線性鏈後,則插入/刪除/查找操作最壞情況運行時間就變爲了O(n)。
因此就有了紅黑樹, 他的最終查找、插入、刪除的時間複雜度最壞情況下依然爲O(logn), 而紅黑樹之所以這麼牛的原因就是它在二叉查找樹的基礎上增加了着色和相關的性質, 這就是紅黑規則:
紅黑規則
1.每一個節點不是紅色的就是黑色的;
2.根總是黑色的;
3.如果節點是紅色的, 則它的子節點必須是黑色的;
4.從根到葉節點的每條路徑, 必須包含相同數目的黑色節點(黑高度相同);
5.每個葉結點(葉結點即指樹尾端NIL指針或NULL結點)是黑的;
根據規則4, 新增節點必須爲紅;
根據規則3, 新增節點之父節點必須爲黑.
當新節點根據二叉樹的規則到達插入點, 卻未能符合上述紅黑規則時, 就必須調整顏色並旋轉樹形;
紅黑結點
- 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; //節點值
- RedBlackNode *left;
- RedBlackNode *right;
- //紅黑樹的顏色
- int color;
- };
紅黑樹
- template <typename Type>
- class RedBlackTree
- {
- public:
- //爲結點定義別名
- typedef RedBlackNode<Type> Node;
- enum {RED, BLACK};
- public:
- //此時紅黑樹所支持的操作尚不完善,
- //而且函數功能(如析構函數, insert操作)
- //實現的也並不理想,
- //但隨着紅黑樹代碼的不斷演進,
- //這些代碼會不斷的完善
- RedBlackTree(const Type &negInf);
- ~RedBlackTree();
- void insert(const Type &data);
- private:
- //爲了在紅黑樹尚未完成的時候進行測試
- //故將之作爲public, 待紅黑樹完成之時,
- //就是將其改爲private之時;
- public:
- //指向紅黑樹的頭(僞根節點)
- //RedBlackNode<Type> *header;
- Node *header;
- //空節點
- Node *nullNode;
- //在插入過程中需要的指針
- Node *current; //當前節點
- Node *parent; //父節點
- Node *grand; //祖父節點(爺爺)
- Node *great; //曾祖父節點(爺爺的爸爸)
- /* 單旋轉 */
- //帶着左孩子旋轉: 向右旋轉
- void rotateWithLeftChild(Node *& k2) const;
- //帶着有孩子旋轉: 向左旋轉
- void rotateWithRightChild(Node *& k1) const;
- /* 雙旋轉 */
- //向右轉
- void doubleRotateWithLeftChild(Node *& k3) const;
- //向左轉
- void doubleRotateWithRightChild(Node *& k1) const;
- };
構造與析構
- //構造函數
- template <typename Type>
- RedBlackTree<Type>::RedBlackTree(const Type &negInf)
- {
- nullNode = new Node();
- header = new Node(negInf, nullNode, nullNode);
- }
- //這一版的析構函數實現並不完善,在後續的版本我們會繼續完善該析構函數
- template <typename Type>
- RedBlackTree<Type>::~RedBlackTree()
- {
- delete nullNode;
- delete header;
- }
一個二叉查找樹的insert
- //這時的insert其實就是一個普通的
- //二叉查找樹的insert, 完全沒要照顧到
- //二叉樹的平衡, 以及紅黑規則的實現
- 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;
- else
- parent->right = current;
- //在後續的版本上,需要加上自動平衡(即實現紅黑規則) -> 紅黑樹
- }
單旋轉
注意:內側孫子節點橫向移動(注意下圖結點37);
左(單)旋:
當在某個結點k1上,做左旋操作時,我們假設它的右孩子k2不是NIL[T](k1可以是任何不是NIL[T]的左孩子結點); 左旋以k1到k2之間的鏈爲“支軸”進行,它使k2成爲該子樹新的根,而k2的左孩子B則成爲k1的右孩子。
- //實現
- //向左轉
- template <typename Type>
- void RedBlackTree<Type>::rotateWithRightChild(Node *& k1) const
- {
- Node *k2 = k1->right;
- //結點B橫向移動
- k1->right = k2->left;
- //令k2提領k1
- k2->left = k1;
- //令k2爲根(使k2替代k1的位置)
- k1 = k2;
- }
右(單)旋:
過程與左旋類似;
- //實現
- //向右轉
- template <typename Type>
- void RedBlackTree<Type>::rotateWithLeftChild(Node *& k2) const
- {
- //首先將B橫向移動
- Node *k1 = k2->left;
- k2->left = k1->right;
- //令k1提領k2
- k1->right = k2;
- //令k1爲根(使k1替代k2的位置)
- k2 = k1;
- }
測試(在完成單旋轉之後):
(構造一顆二叉查找樹樹如圖所示, 左邊爲尚未旋轉之前, 右爲旋轉之後)
- //測試代碼如下:
- int main()
- {
- //用NEG_INF來代表負無窮大
- const int NEG_INF = -999999;
- RedBlackTree<int> tree(NEG_INF);
- //單旋轉時候的測試數據
- 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
- //向右旋轉
- cout << "向右旋轉" << endl;
- tree.rotateWithLeftChild(tree.header->right);
- cout << tree.header->right->element << endl; //15
- cout << tree.header->right->right->element << endl; //30
- cout << tree.header->right->right->left->element << endl; //20
- cout << tree.header->right->right->right->element << endl; //70
- //然後再向左轉(又轉了回來)
- cout << "向左旋轉" << 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
- }
雙旋轉
單旋轉有時會出現一個問題(如下圖所示):
(如果內側子孫節點[k1]過深, 則將其單向移動是不會解決問題的)
於是就有了雙旋轉
向右雙旋轉:
1.首先以k1爲軸, k1與k2向左旋轉;
2.然後以k3爲軸, k3與旋轉之後的k1向右旋轉;
- //實現
- //向右雙旋轉
- template <typename Type>
- void RedBlackTree<Type>::doubleRotateWithLeftChild(Node *& k3) const
- {
- //首先將其左兒子(k1)向左單旋轉
- rotateWithRightChild(k3->left);
- //然後將自己(k3)向右單旋轉
- rotateWithLeftChild(k3);
- }
向左雙旋轉:
1.首先以k3爲軸, k2與k3向右旋轉;
2.然後以k1爲軸, k1與旋轉之後的k2向左旋轉;
- //實現
- //向左雙旋轉
- template <typename Type>
- void RedBlackTree<Type>::doubleRotateWithRightChild(Node *& k1) const
- {
- //首先將其右兒子(k2)向右單旋轉
- rotateWithLeftChild(k1->right);
- //然後將自己(k1)向左單旋轉
- rotateWithRightChild(k1);
- }
- //注:其實紅黑樹中並沒有用到雙旋轉, 而是自己實現了一個rotate操作,
- //在此只爲了學習雙旋轉的理論;
測試(在完成雙旋轉之後):
(構造一顆二叉查找樹樹如圖所示, 左邊爲尚未旋轉之前, 右爲旋轉之後, 以8爲軸進行雙旋轉)
- int main()
- {
- //用NEG_INF來代表負無窮大
- const int NEG_INF = -999999;
- RedBlackTree<int> tree(NEG_INF);
- //雙旋轉時的測試數據
- 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; //12
- cout << tree.header->right->left->element << endl; //8
- cout << tree.header->right->right->element << endl; //16
- cout << tree.header->right->left->left->element << endl; //4
- cout << tree.header->right->left->right->element << endl; //10
- cout << tree.header->right->right->left->element << endl; //14
- // cout << tree.header->right->left->right->left->element << endl; //5曾經做過哨兵
- // cout << tree.header->right->left->right->right->element << endl; //5
- cout << tree.header->right->left->left->left->element << endl; //2
- cout << tree.header->right->left->left->right->element << endl; //6
- cout << tree.header->right->left->left->right->left->element << endl; //5
- cout << "\n向右雙旋轉" << endl;
- //以8爲基準向右雙旋轉
- tree.doubleRotateWithLeftChild(tree.header->right->left);
- cout << tree.header->right->element << endl; //12
- cout << tree.header->right->left->element << endl; //6
- cout << tree.header->right->right->element << endl; //16
- cout << tree.header->right->left->left->element << endl; //4
- cout << tree.header->right->left->right->element << endl; //8
- cout << tree.header->right->right->left->element << endl; //14
- cout << tree.header->right->left->left->left->element << endl; //2
- cout << tree.header->right->left->left->right->element << endl; //5
- cout << tree.header->right->left->right->right->element << endl; //10
- }
實現紅黑樹用到的異常
- 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) {}
- };
原文地址:http://blog.csdn.net/zjf280441589/article/details/43865375