紅黑樹是一棵二叉搜索樹,它在每個節點上增加了一個存儲位來表示節點的顏色,可以是Red或Black。
通過對任何一條從根到葉子節點簡單路徑上的顏色來約束樹的高度,紅黑樹保證最長路徑不超過最短路徑的兩倍,因而近似於平衡。
紅黑樹是滿足下面紅黑性質的二叉搜索樹:
1. 每個節點,不是紅色就是黑色的
2. 根節點是黑色的
3. 如果一個節點是紅色的,則它的兩個子節點是黑色的(不存在連續的紅色節點)
4. 對每個節點,從該節點到其所有後代葉節點的簡單路徑上,均包含相同數目的黑色節點。
思考:為什麼滿足上面的顏色約束性質,紅黑樹能保證最長路徑不超過最短路徑的兩倍?
最短的路徑上節點的顏色全部都為黑色;最長的路徑則為黑紅交叉的路徑,其上有與最短路徑的黑節點數目相同的黑節點數和紅節點數目。所以我們按照紅黑樹性質所建立的紅黑樹的最長路徑必然不會超過最短路徑的兩倍!
建立紅黑樹的節點類:
插入的新節點默認是紅色的。原因是:插入黑節點必然會影響所有路徑都含有相同數目的黑色節點這一原則,較難維護!
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25enum
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的左孩子
插入節點:
基於以上的情況,紅黑樹利用模板類封裝的插入函數算法就完成了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105template
<
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樹,所以實際運用 中紅黑樹更多。