紅黑樹是一顆二叉搜索樹,它在每個節點上增加了一個存儲位來表示節點的顏色,可以是red或black。通過對任何一條從根節點到葉子節點的簡單路徑上的顏色來約束,紅黑樹保證了最長路徑不超過最短路經的兩倍,因此近似於平衡。
紅黑樹的規則:
1、每個節點不是紅色就是黑色的。
2、根結點是黑色的。
3、如果一個節點是紅色的,則它的兩個子結點是黑色的。即每條路徑上不能存在兩個連續的紅節點。
4、對每個節點,從該節點到其他節點的簡單路徑上,均包含相同數目的黑色節點。
紅黑樹節點RBTreeNode的實現,利用三叉鏈(left、right、parent)、key、value及顏色col。
enum colour
{
RED,
BLACK,
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
K _key;
V _value;
colour _col;
RBTreeNode(const K& key = K(), const V& value = V())
:_left(NULL)
, _right(NULL)
, _parent(NULL)
, _key(key)
, _value(value)
, _col(RED)//初始化插入節點的顏色爲紅色,不影響黑節點個數
{}
};
一、紅黑樹的插入
紅黑樹的插入類似於二叉搜索樹,但是每次插入後都到要注意是否滿足紅黑樹的規則,特別是規則3和規則4。如果不滿足就需要調整樹的結構,下面對插入節點時分成以下幾種情況:
注:cur(插入節點)parent(cur的父親節點)grandfather(parent的父親節點)uncle(父親的兄弟節點)
1、根節點root爲空,直接插入新節點並給root,設置根節點的顏色爲BLACK。
2、根節點root不爲空,找到插入節點的位置並插入節點cur。cur節點是紅色,若parent是紅節點,則需要進行調整。
存在以下三種情況:
情況一:cur爲紅,parent爲紅,grandfather爲黑,uncle存在且爲紅。
調整方案,如下如所示:
情況二:cur爲紅,parent爲紅,grandfather爲黑,uncle不存在或者uncle爲黑。
左單旋:parent爲grantfather的右孩子,cur爲parent的右孩子。
右單旋: parent爲grantfather的左孩子,cur爲parent的左孩子。
調整方案,下面以右單旋進行分析,如下圖所示:
左單旋轉類似右單旋轉的實現過程。
情況三:cur爲紅,parent爲紅,grantfather爲黑,uncle不存在或者uncle爲黑。
左右單旋:parent爲grantfather的右孩子,cur爲parent的左孩子。
右左單旋:parent爲grantfather的左孩子,cur爲parent的右孩子。
調整方案,下面以做右單旋進行分析,如下圖所示:
右左旋轉的實現類似左單旋轉的實現過程。
具體實現如下:
RBTree()
:_root(NULL)
{}
bool Insert(const K& key, const V& value)
{
//1、_root爲空時,插入節點是根節點
if (_root == NULL)
{
_root = new Node(key, value);
_root->_col = BLACK;
return true;
}
Node* parent = NULL;
Node* cur = _root;
while (cur)//找到插入節點的位置
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
cur = new Node(key, value);//插入節點
if (parent->_key > cur->_key)
{
parent->_left = cur;
cur->_parent = parent;
}
if (parent->_key < cur->_key)
{
parent->_right = cur;
cur->_parent = parent;
}
//2、出現兩個連續紅節點,進行調整
while (cur != _root && parent->_col == RED)//此條件說明存在parent節點,parent存在父親節點
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)//情況一:uncle爲RED
{
grandfather->_col = RED;
parent->_col = BLACK;
uncle->_col = BLACK;
}
else //情況二、三:uncle爲BLACK或不存在(右單旋或左右單旋)
{//先考慮左右單旋,先進行左單旋,轉化爲情況二,再進行右單旋
if (cur == parent->_right)
{
_RotateL(parent);//左旋不需要變顏色
}
_RotateR(grandfather);
}
}
else
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_right)
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)//情況一:uncle爲RED
{
grandfather->_col = RED;
parent->_col = BLACK;
uncle->_col = BLACK;
}
else //情況二、三:uncle爲BLACK或不存在(左單旋或右左單旋)
{
if (cur == parent->_left)
{
_RotateR(parent);//右旋不需要變顏色
}
_RotateL(grandfather);
}
}
}
cur = grandfather;
parent = cur->_parent;
_root->_col = BLACK;
}
}
void _RotateL(Node* parent)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
parent->_right = SubRL;
if (SubRL)
SubRL->_parent = parent;
SubR->_parent = parent->_parent;
SubR->_left = parent;
parent->_parent = SubR;
//變色
SubR->_col = BLACK;//情況二中:parent變黑,grandfather變紅
parent->_col = RED;
parent = SubR;
if (parent->_parent == NULL)
_root = parent;
else
{
Node* ppNode = parent->_parent;
if (ppNode->_key > parent->_key)
ppNode->_left = parent;
else
ppNode->_right = parent;
}
}
void _RotateR(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
parent->_left = SubLR;
if (SubLR)
SubLR->_parent = parent;
SubL->_parent = parent->_parent;
SubL->_right = parent;
parent->_parent = SubL;
//變色
SubL->_col = BLACK;//情況二中:parent變黑,grandfather變紅
parent->_col = RED;
parent = SubL;
if (parent->_parent == NULL)
_root = parent;
else
{
Node* ppNode = parent->_parent;
if (ppNode->_key > parent->_key)
ppNode->_left = parent;
else
ppNode->_right = parent;
}
}
二、紅黑樹的判斷
1、根結點是否滿足紅黑樹規則,是否爲黑色。
2、每條路徑的黑色節點相等。統計出一條路徑的黑色節點的個數,然後與其他路徑黑色節點個數進行比較。
3、不存在連續的紅色節點,判斷紅色節點的父親節點是否爲紅色。
具體實現如下:
bool Check()
{
if (_root->_col == RED)
return false;
int count = 0;//統計出一條路徑的黑色節點的個數
int num = 0;//需要與count比較的其他路徑黑色節點個數
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
count++;
cur = cur->_left;
}
return _Check(_root, count, num);
}
bool _Check(Node* root, int BlackNum, int CurBlackNum)
{
if (root == NULL)
return true;
if (root->_col == RED && root->_parent->_col == RED)//存在兩個連續的紅節點
return false;
if (root->_col == BLACK)//黑節點就CurBlackNum++
CurBlackNum++;
if (root->_left == NULL && root->_right == NULL)
{
if (CurBlackNum == BlackNum)
return true;
else//黑色節點不相等返回false
return false;
}
return _Check(root->_left, BlackNum, CurBlackNum)
&& _Check(root->_right, BlackNum, CurBlackNum);//進行左右遞歸
}
紅黑樹的效率:
1、最壞情況下,紅黑樹高度不超過2lgN
最壞的情況就是,紅黑樹中除了最左側路徑全部是由3-node節點組成,即紅黑相間的路徑長度是全黑路徑長度的2倍。
2、紅黑樹的平均高度大約爲lgN
紅黑樹的運用(高效的二叉搜索樹)
紅黑樹這種數據結構應用十分廣泛,在多種編程語言中被用作符號表的實現,如:
- Java中的java.util.TreeMap,java.util.TreeSet
- C++ STL中的:map,multimap,multiset
- .NET中的:SortedDictionary,SortedSet 等
紅黑樹和AVL樹的比較
1、紅黑樹和AVL樹都是高效的平衡二叉樹,增刪查改的時間複雜度都是O(lg(N))
2、紅黑樹的不追求完全平衡,保證最長路徑不超過最短路徑的2倍,相對而言,降低了旋轉的要求,所以性能優於AVL樹,所以實際運用中紅黑樹更多。紅黑樹是一種特殊的二叉查找樹,他的查找方法也和二叉查找樹一樣,不需要做太多更改,但是由於紅黑樹比一般的二叉查找樹具有更好的平衡,所以查找起來更快。
以上爲個人學習的一些總結,如有紕漏,請多多指教。