紅黑樹是一棵二叉搜索樹,它在每個節點上增加了一個存儲位來表示節點的顏色,可以是Red或Black。
通過對任何一條從根到葉子節點簡單路徑上的顏色來約束樹的高度,紅黑樹保證最長路徑不超過最短路徑的兩倍,因而近似於平衡。
紅黑樹是滿足下面紅黑性質的二叉搜索樹:
1. 每個節點,不是紅色就是黑色的
2. 根節點是黑色的
3. 如果一個節點是紅色的,則它的兩個子節點是黑色的(不存在連續的紅色節點)
4. 對每個節點,從該節點到其所有后代葉節點的簡單路徑上,均包含相同數目的黑色節點。
思考:為什么滿足上面的顏色約束性質,紅黑樹能保證最長路徑不超過最短路徑的兩倍?
最短的路徑上節點的顏色全部都為黑色;最長的路徑則為黑紅交叉的路徑,其上有與最短路徑的黑節點數目相同的黑節點數和紅節點數目。所以我們按照紅黑樹性質所建立的紅黑樹的最長路徑必然不會超過最短路徑的兩倍!
建立紅黑樹的節點類:
插入的新節點默認是紅色的。原因是:插入黑節點必然會影響所有路徑都含有相同數目的黑色節點這一原則,較難維護!
enum Color
{
RED,
BLACK
};
template<class K,class V>
struct RBTreeNode
{
K _key;
V _value;
Color _color; //顏色
RBTreeNode<K, V>* _left; //指向左孩子的指針
RBTreeNode<K, V>* _right; //指向右孩子的指針
RBTreeNode<K, V>* _parent; //指向父節點的指針
RBTreeNode(const K& key=K(), const V&value=V())
:_key(key)
, _value(value)
, _color(RED)
, _left(NULL)
, _right(NULL)
, _parent(NULL)
{}
};
紅黑樹需要變色或利用旋轉來降低高度的幾種情況:
圖注:g代表grandfather祖父節點;p代表parent父親結點;u代表uncle叔叔節點;cur代表當前節點
一、父節點是祖父節點的左孩子
1.uncle的顏色是紅色
①當前節點cur是parent的左孩子

②當前節點cur是parent的右孩子

2.uncle的顏色是黑色 或者 uncle為NULL
①cur是parent的左孩子,右單旋

②cur是parent的右孩子,先左后右雙旋

二、父節點是祖父節點的右孩子
1.uncle的顏色是紅色
①cur是parent的右孩子

②cur是parent的左孩子

2.uncle的顏色是黑色 或者 uncle為NULL
①cur是parent的右孩子

②cur是parent的左孩子

插入節點:
- 首先,要找到插入該節點的位置,找到后就插入節點
- 然后,對紅黑樹的節點顏色的合法性進行檢查,並根據檢查結果進行變色或者旋轉。
基於以上的情況,紅黑樹利用模板類封裝的插入函數算法就完成了:
template<class K, class V>
bool RBTree<K, V>::Insert(const K& key, const V& value)
{
if (_root == NULL)
{
_root = new RBTreeNode<K, V>(key, value);
_root->_color = BLACK;
return true;
}
// 找位置
RBTreeNode<K, V>* cur = _root;
RBTreeNode<K, V>* parent = NULL;
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 RBTreeNode<K, V>(key, value);
cur->_parent = parent;
if (parent->_key > key)
parent->_left = cur;
else if (parent->_key < key)
parent->_right = cur;
//檢查顏色分配是否滿足要求
while (parent&&parent->_color==RED)
{
RBTreeNode<K, V>* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
RBTreeNode<K, V>* uncle = grandfather->_right;
if (uncle&&uncle->_color == RED)
{ //第一種情況 變色
grandfather->_color = RED;
parent->_color =BLACK;
uncle->_color = BLACK;
cur = grandfather;
parent = grandfather->_parent;
}
else if( (uncle&&uncle->_color==BLACK)||uncle==NULL)
{
if (cur == parent->_left)
{//第二種情況 右單旋 cur必然有黑色孩子
parent->_color = BLACK;
grandfather->_color = RED;
RotateR(grandfather);
}
else
{//第三種情況 左右雙旋
RotateL(parent);
parent->_color = BLACK;
grandfather->_color = RED;
RotateR(grandfather);
}
break;
}
}
else if (parent == grandfather->_right)
{
RBTreeNode<K, V>* uncle = grandfather->_left;
if (uncle&&uncle->_color == RED)
{//第一種情況 變色
grandfather->_color = RED;
parent->_color = BLACK;
uncle->_color = BLACK;
cur = grandfather;
parent = cur->_parent;
}
else if( (uncle&&uncle->_color == BLACK)||uncle==NULL)
{//第二種情況 左單旋 cur必然有黑孩子
if (cur == parent->_right)
{
parent->_color = BLACK;
grandfather->_color = RED;
RotateL(grandfather);
}
else if (cur==parent->_left)
{//第三種情況 右左雙旋
RotateR(parent);
parent->_color = BLACK;
grandfather->_color = RED;
RotateL(grandfather);
}
break;
}
}
}
_root->_color = BLACK;
return true;
}
插入完成之后,我們無法直觀的看出紅黑樹的節點顏色是否合法,也無法直觀的看出每條路徑的黑色節點數目是否相同。
所以,這里實現兩個函數,方便檢驗紅黑樹的合法性。
- 紅黑樹每條路徑的黑色節點數目都相同,所以隨意遍歷一條路徑,計算這條路上的黑色節點的數目。以該數據為標桿,和其他路徑的黑色節點數目作比較,判斷是否都相同。
- 如果當前節點是紅顏色並且它有父節點,那么再判斷父節點的顏色是否也是紅色,這樣就能判斷該樹是否滿足連續兩個節點不能同時為紅色這一性質。
//檢驗紅黑樹的合法性
template<class K, class V>
bool RBTree<K, V>::Check()
{
//統計紅黑樹每條路徑上黑色節點的數量
int blackNum = 0;
RBTreeNode<K, V>* cur = _root;
while (cur)
{
if (cur->_color == BLACK)
blackNum++;
cur = cur->_left;
}
int CBNum = 0;
return _Check(_root,blackNum,CBNum);
}
////////////////// 遞歸輔助
template<class K, class V>
bool RBTree<K, V>::_Check(RBTreeNode<K, V>* root, int blackNum, int CBNum)
{
if (root == NULL)
return true;
if (root->_color == BLACK)
{
CBNum++;
if (root->_left == NULL&&root->_right == NULL)
{ //走到了葉子節點 將該條路徑上的黑色節點數量與之前統計的黑色節點數量做比較
if (blackNum == CBNum)
{
return true;
}
else
{
cout << "葉子節點為" << root->_key << "路徑的黑色節點數目與最左側支路的黑色節點數目不相等 !" << endl;
return false;
}
}
}
else if (root->_parent&&root->_parent->_color == RED)
{//判斷是否存在連續的兩個紅色節點
cout << root->_parent->_key << " 和 " << root->_key << " 為兩個連續的紅色節點" << endl;
return false;
}
//遞歸檢驗子路
return _Check(root->_left, blackNum, CBNum) && _Check(root->_right, blackNum, CBNum);
}
紅黑樹和AVL樹的比較
紅黑樹和AVL樹都是高效的平衡二叉樹,增刪查改的時間復雜度都是O(lg(N)) 紅黑樹的不追求完全平衡,保證最長路徑不超過最短路徑的2倍,相對而言,降低了旋轉的要求,所以性能會優於AVL樹,所以實際運用 中紅黑樹更多。
