紅黑樹的概念
紅黑樹,是一種二叉搜索樹
,但在每個結點上增加一個存儲位表示結點的顏色,可以是Red
或
Black
。 通過對任何一條從根到葉子的路徑上各個結點著色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出倆倍,因而是接近平衡的。
對比一下:
AVLTree ,是嚴格平衡,左右的高度差 不超過 1
紅黑樹,是近似平衡,最長路徑不會超過最短路徑的 2倍
紅黑樹的性質
- 每個結點不是紅色就是黑色
- 根節點是黑色的
- 如果一個節點是紅色的,則它的兩個孩子結點是黑色的
就是說,如果父節點是紅色的,他的兩個孩子也必須是紅色的,不能出現連續的紅色節點,即:父子節點的顏色只有這幾種可能:父(黑) + 子(黑),父(黑)+ 子(紅),父(紅)+ 子(黑),
- 對于每個結點,從該結點到其所有后代葉子結點的簡單路徑上,均包含相同數目的黑色結點
意思就是每條路徑上都有相同數量的黑色節點
- 每個葉子結點都是黑色的(此處的葉子結點指的是空結點)
這里的葉子節點不是指的傳統意義上的葉子節點,而是指的空結點,也稱NIL結點,這個NIL 節點的作用是方便我們數路勁,就是數這棵樹有幾條路徑,比如下面的例子:
思考:為什么滿足上
我們來分析一下,什k面的性質,紅黑樹就能保證:其最長路徑中節點個數不會超過最短路徑節點個數的兩倍? 🔎
我們來分析一下,什么情況下,路徑是最短的?
根據性質4每條路徑上都包含相同數目的黑色節點
, 那就說明如果一條路徑上只有黑色節點,沒有紅色節點的話,更其他那些有紅色節點的路勁相比,它就是最短的。
所以:
路勁最短 : 只有黑色節點的情況
我們再來分析一下,什么情況下,路徑是最長的呢?
在相同黑色節點的條件下,紅色節點的個數越多,路勁就越長,根據性質3,要盡可能的增加紅色節點的個數的的話,它們之間的搭配條件要盡可能滿足:父(黑)+ 子(紅)+ 孫子(黑)+ 重孫子(紅),這樣的紅黑紅黑的交替的情況,所以最長的路徑就是最短路徑的2倍
路徑最長 : 在黑色節點數目一定的情況下,紅色節點的數目最多
綜上所述,假設有N個黑色的節點,每條路徑的的節點的個數的范圍是[N,2N]
📌紅黑樹節點的定義
的定義:
enum Color{RED,BLACK};
// ekkkkk
kkkkk:
```c+num 是一個枚舉類型,第一個RED是第一kkkkkk'k'k'k+
enum Color{RED,BLACKk'k'k'k;
// enum 是一個枚舉類型,第一個RED是第一個枚舉值,默認是 0、
// 第二個BLACK 是第二個枚舉值,kkkkk
k'k'k'k
紅黑樹節點的定義:
```c++
template <class K, class V>
struct RBTreeNode
{RBTreeNode<K, V> *_left;RBTreeNode<K, V> *_right;RBTreeNode<K, V> *_parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V> &kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};
🍉 上面的構造函數中,為什么默認節點的顏色要給成 紅色呢?
- 保持樹的黑色高度不變。
因為紅黑數的性質4: 對于每個結點,從該結點到其所有后代葉子結點的簡單路徑上,均包含相同數目的黑色結點- 減少插入時的調整操作:
如果插入的是黑色節點,需要進行更多的調整操作來恢復紅黑樹的性質
📌紅黑樹的插入操作
template <class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;public:bool Inster(const pair<K, V> &kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK; // 如果是空的,那我們要插入一個節點,這個節點的顏色是黑色,因為性質2,跟節點的顏色是黑色的。return true;}Node *parent = nullptr;Node *cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_kv.first < kv.first;cur->_parent = parent;}else if{parent->_left = cur;cur->parent = parent;}return true;}private:Node *_root = nullptr;
};
我們插入節點的的時候,如果插入的是一個紅色的節點,但是這個新增節點的父親是黑色的,那么插入結束,我們可以不做任何處理。
但是,如果我們插入一個紅色的節點,但是他的父親也是紅色的,這個時候該怎么處理呢?
以上的這種情況是違反規則 3 的,所以,我們必然要做的一步就是:把新增節點的父親變成黑色 節點,然后,我們在根據規則對其他節點顏色的顏色進行相應的調整以維護紅樹黑樹的性質
這么看來紅黑樹的插入操作挺麻煩的呀,那為之奈何呀?
其實也有一定的規律在里面,下面我們細細的分析
首先,我們新增一個節點,我們選擇的是新增紅色的節點,這樣對整個樹的影響會小一些
如果新增節點的父親是黑色的,那我們不需要處理。
如果新增節點的父親是紅色的,那我們需要處理,處理的方式可能是:1. 旋轉 2. 變色
有可能只變色就可以了,也有可能既要旋轉又要變色。
第一種情況:
新增節點是紅色的,新增節點的父親也是紅色的,祖父是黑色節點。并且叔叔存在而且叔叔也是紅色的;
我們的解決辦法是,
將父親節點和叔叔節點變成紅色,
祖父節點:
- 如果祖父節點是這棵樹的根節點,那就必須是黑色,所以保持顏色不變。
- 如果祖父節點也只是這棵樹的一個局部,那我們要把祖父節點變成紅色的,這樣才能保持這條路徑中黑色節點的個數不變。
但是,這樣我們忽略了一個問題:如果祖父節點的父親(曾祖父)是紅色的咋辦?如下圖:
遇到這種情況,首先,如果曾祖父是紅色的,說明曾祖父不是根節點,因為根節點一定是黑色的,所以我們要往上走,把曾祖父當成當前節點,然后繼續的看曾祖父的父節點和祖父節點,這樣循環的往上處理。
第二種情況:
當前的節點是紅色,父親節點是紅色,祖父節點是黑色,叔叔節點要么不存在,要么存在是黑色
cur : 表示當前節點
p : parent 表示父親節點
g : grandpa 表示祖父節點
u : uncle 表示叔叔節點
- 當前節點是紅色,父親節點是紅色,祖父節點是黑色,叔叔節點不存在:
解決方案:
parent 是 grandpa 的左孩子,cur 是parent 的左孩子,進行右單旋轉
parent 是 grandpa 的右孩子,cur 是 parent的右邊孩子,進行左單旋轉
parent 和 grandpa 變色
- 如果叔叔存在且為黑色:
這種情況,不止需要變色,還需要旋轉
假設每條路徑黑色節點的數量
h
個,
h
<=紅黑樹中任意一條路徑長度
<=2h
最短路徑:h
最長的路徑:2h
最長路徑和最短路徑是不一定存在的,紅黑樹是一種近似平衡
完整代碼:
RBTree.h
#pragma once
#pragma onceenum Colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}// 新增節點給紅色cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){// g// p u// cNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// 變色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 繼續往上更新處理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_left){// 單旋// g// p// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// 雙旋// g// p// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else // parent == grandfather->_right{// g// u p // c//Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){// 變色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 繼續往上處理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p // c//RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (subRL)subRL->_parent = parent;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}// 根節點->當前節點這條路徑的黑色節點的數量bool Check(Node* root, int blacknum, const int refVal){if (root == nullptr){//cout << balcknum << endl;if (blacknum != refVal){cout << "存在黑色節點數量不相等的路徑" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "有連續的紅色節點" << endl;return false;}if (root->_col == BLACK){++blacknum;}return Check(root->_left, blacknum, refVal)&& Check(root->_right, blacknum, refVal);}bool IsBalance(){if (_root == nullptr)return true;if (_root->_col == RED)return false;//參考值int refVal = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refVal;}cur = cur->_left;}int blacknum = 0;return Check(_root, blacknum, refVal);}private:Node* _root = nullptr;
};