紅黑樹簡介
node
紅黑樹是一種自平衡二叉查找樹,也有着二叉搜索樹的特性,保持着右邊始終大於左邊結點key的特性。前面提到過的AVL樹,也是二叉搜索樹的一種變形,紅黑樹沒有達到AVL樹的高度平衡,換句話說,它的高度,並無AVL樹那麼高的要求,但他的應用卻更加的普遍,實踐中是至關高效的,他能夠在O(log n)的時間內作查找、插入、刪除操做。在C++ STL中,set、multiset、map、multimap等都應用到的紅黑樹的變體。算法
紅黑樹在平衡二叉搜索樹的前提下,每一個節點新增了 _color 這一成員變量,用來對各個節點作出標記。接下來,咱們就來分析紅黑樹的插入算法。
ide
一棵AVL樹,須要知足如下幾條要求。
函數
一、每一個結點,不是黑色就是紅色
ui
二、樹的根結點必須是黑色
spa
三、從根節點到葉子結點的任意一條路上,不容許存在兩個連續的紅色結點。
指針
四、對於每一個結點,從他開始到每一個葉結點的簡單路徑上,黑色結點樹相同。blog
這裏多說一點,若是知足以上條件的話,從根節點開始,到葉子結點,最長的不會超過最長路徑的兩倍。(能夠考慮最爲極端的狀況)
get
思路簡析it
和AVL樹相同,要保證樹的平衡性,必需要用到的是旋轉算法。因爲紅黑樹的狀況比較多(儘管寫起代碼來不是很複雜),因此在這裏旋轉的過程當中,咱們不像AVL樹同樣,旋轉的同時對平衡因子進行調整,紅黑樹的旋轉算法,只是單純調整當前結點與其parent 、grandparent 、uncle結點的相對位置,在旋轉完成以後,咱們再對結點顏色進行設置。
插入算法會在下面給出。
首先咱們給出結點的定義。
enum Color
{
RED,
BLACK
};
template<typename K, typename V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
K _key;
V _value;
Color _color;
RBTreeNode(const K& key,const V& value)
:_left(NULL)
, _right(NULL)
, _parent(NULL)
, _key(key)
, _value(value)
, _color(RED)//默認構造紅色結點
{}
};
_key爲關鍵碼(_key值是不容許重複的),_value爲值,關於這裏結點的構造函數,想多說一點,爲何結點顏色要默認給紅色?很明顯,通常狀況下,黑色結點比紅色結點多,但這裏咱們須要注意的是,咱們針對的調整,其實大多數是紅色。黑色結點下若是追加了紅色結點,是不須要調整的,紅色結點下若是多增長了一個黑色結點,是必定要進行調整的。
接下來開始插入結點。
一、處理特殊狀況
當樹爲空樹時,直接 new 一個結點給根,而後再改變顏色便可。
if (_root == NULL) { _root = new Node(key, value); _root->_color = BLACK; return true; }
二、樹不爲空樹時,咱們首先須要找到咱們待插入結點的位置。因爲紅黑樹是二叉搜索樹,經過循環,比較待插入結點的key值和當前結點的大小,找到待插入結點的位置。同時給該節點開闢空間,肯定和parent節點的指向關係。
Node* cur = _root; Node* parent = NULL; while (cur != NULL) { if (key > cur->_key) { parent = cur; cur = cur->_right; } else if (key < cur->_key) { parent = cur; cur = cur->_left; } else { return false; } } cur = new Node(key, value); if (key > (parent->_key)) { parent->_right = cur; cur->_parent = parent; } else { parent->_left = cur; cur->_parent = parent; }
當插入結點的parent結點爲黑色結點時,不須要作任何調整,只須要和parent結點創建聯繫便可。
三、下面是須要咱們特殊處理的幾種狀況。
咱們給出四個Node結點 cur(待插入結點)、parent (cur的父親結點)、grandparent(cur的祖父結點)、uncle(cur的叔叔結點)。
狀況1、
parent爲黑色,uncle存在且爲紅色
如圖:
三角形結點只是表示可能存在的結點,可能爲空。
當cur爲新插入結點時,a-e結點均爲空結點,因爲不能夠存在連續的紅結點,所以,咱們須要將parent結點和uncle結點變爲黑色。細心的話能夠發現,grandparent結點變爲了紅色,這是由於當grandparent不爲根節點時,咱們這棵子樹的一條支路上的黑色結點就會多出一個,所以咱們須要將grandparent結點變爲紅色,而後繼續向上進行調整。在插入完成以後,咱們只須要統一將根節點從新賦值爲紅色便可。
狀況2、
parent爲紅色,uncle結點不存在,或uncle結點存在,但爲黑色
如圖:
看到第一張圖的時候,不要懷疑這裏畫的有問題,這種狀況是可能存在的,那就是說,cur是調整上來的,從個人上一種狀況調整過來的,雖然看着grandparent的左右支路黑色結點數不相同,但我還有下面的三角形結點。
如今我這裏就須要進行旋轉,爲何這裏不能直接顏色變換?由於咱們拋過三角形結點,以grandparent結點爲分界,最左支路和最後支路的,黑色結點數差一。旋轉的圖示如上圖所示,以grandparent結點爲軸,向右旋轉。將grandparent結點做爲parent結點的右子樹進行旋轉。同時須要的是,grandparent結點不必定是根節點,咱們須要提早保留並判斷grandparent->_parent結點,以後從新賦給parent->_parent。
狀況3、
若是能夠理解了第二種狀況,第三種狀況就容易理解了許多,和第二種狀況同樣,只不過cur是parent的右子樹,咱們須要先以parent爲軸,向左旋轉,獲得上面這種狀況以後,再以grandparent爲軸向右旋轉。以下圖。
值得注意的一點,也是一開始寫代碼老是驗證出錯的一個問題,咱們先以parent爲軸左旋,以後看上圖,cur此時變成了parent->_parent,若是此時按照狀況二的處理方式,結點顏色必定會發生問題,所以,在上圖中,我專門給出了一張圖,將parent和cur指針交換,注意,只交換的是指針。
到這裏,紅黑樹的基本狀況以及處理完畢,再有的話就是當parent一開始就是在grandparent的右子樹上的幾種狀況,和上面的旋轉成鏡像的關係。下面給出具體的代碼:
bool Insert(const K& key,const V& value) { //空樹 if (_root == NULL) { _root = new Node(key, value); _root->_color = BLACK; return true; } //構建節點,並插入到對應位置 Node* cur = _root; Node* parent = NULL; while (cur != NULL) { if (key > cur->_key) { parent = cur; cur = cur->_right; } else if (key < cur->_key) { parent = cur; cur = cur->_left; } else { return false; } } cur = new Node(key, value); if (key > (parent->_key)) { parent->_right = cur; cur->_parent = parent; } else { parent->_left = cur; cur->_parent = parent; } //開始調整 while (cur != _root && parent->_color == RED) { //若是parent的color爲RED,parent必定不是根節點,且祖父節點color爲BLACK Node* grandparentnode = parent->_parent;//grandparentnode->_color = BLACK; if (parent == grandparentnode->_left) { Node* unclenode = grandparentnode->_right;//叔叔節點uncle if (unclenode && (unclenode->_color == RED))//uncle不爲空,且uncle->color爲RED { parent->_color = BLACK; unclenode->_color = BLACK; grandparentnode->_color = RED; cur = grandparentnode; parent = cur->_parent; } else//uncle爲空,或uncle->color爲BLACK { if (cur == parent->_right) { RotateL(parent); std::swap(parent, cur); } RotateR(grandparentnode); parent->_color = BLACK; grandparentnode->_color = RED; break; } } else//parent == grandparent->_right { Node* unclenode = grandparentnode->_left; if (unclenode && (unclenode->_color == RED))//uncle存在,且color爲 RED { parent->_color = BLACK; unclenode->_color = BLACK; grandparentnode->_color = RED; cur = grandparentnode; parent = cur->_parent; } else//uncle不存在,或uncle->color爲黑色 { if (cur == parent->_left) { RotateR(parent); std::swap(cur,parent); } RotateL(grandparentnode); grandparentnode->_color = RED; parent->_color = BLACK; break; } } } //統一將根節點的顏色變爲黑色 _root->_color = BLACK; return true; }
紅黑樹結點的插入到這裏就結束了,能夠發現的是,咱們其實一直在關注的是uncle結點,也就是cur的叔叔結點。這是紅黑樹插入思想裏面的一個核心。
下面,就紅黑樹的基本特徵,給出一段檢驗函數,判斷紅黑樹是否知足要求。
bool IsBalance() { if (_root == NULL) return true; if (_root->_color == RED) return false; int count = 0; Node* cur = _root; while (cur != NULL) { if (cur->_color == BLACK) { count++; } cur = cur->_left; } int k = 0; return _IsBalance(_root, count, k); } bool _IsBalance(Node* root, const int& count, int k) { if (root == NULL) return true; if (root != _root && root->_color == RED) { if (root->_parent->_color == RED) { cout << "連續紅色結點" << root->_key << endl; return false; } } if (root->_color == BLACK) k++; if (root->_left == NULL && root->_right == NULL) { if (k == count) return true; else { cout << "黑色節點不相等" << root->_key << endl; return false; } } return _IsBalance(root->_left, count, k) \ && _IsBalance(root->_right, count, k); }
紅黑樹的應用遠比AVL樹多,仍是一開始咱們說的,其實紅黑樹的高度相對來講要比AVL樹高出一些的,但這其實並不影響太多。由於咱們的時間複雜度都是在O(log n)附近,當n = 10億時,log(n)也僅僅只有30。可是另外一方面,因爲紅黑樹要比AVL樹的要求低,因此當咱們插入一個結點時,相對來講調整的次數也就少了許多,這個是紅黑樹的優點。
------muhuizz整理