💓博主CSDN主頁:杭電碼農-NEO💓
?
?專欄分類:C++從入門到精通?
?
🚚代碼倉庫:NEO的學習日記🚚
?
🌹關注我🫵帶你學習C++
? 🔝🔝
紅黑樹
- 1. 前言
- 2. 紅黑樹的概念以及性質
- 3. 紅黑樹為什么更實用?
- 4. 紅黑樹模擬實現代碼框架
- 5. 紅黑樹的插入操作初步分析
- 6. 紅黑樹的插入操作詳解(一)
- 7. 紅黑樹的插入操作詳解(二)
- 8. 紅黑樹的插入代碼實現
- 9. 總結以及拓展
1. 前言
如果說發明AVL樹的人是天才,那么
發明紅黑樹的人可以稱為天才中的
精英!為什么AVL樹這么強大但是沒啥
人用呢?就是因為紅黑樹比你還好!
本章重點:
本篇文章著重講解紅黑樹的概念以及
性質,以及為了維護紅黑樹這種性質而
做的限制條件.最后模擬實現紅黑樹的
插入,帶大家熟悉變色和旋轉規則!
2. 紅黑樹的概念以及性質
紅黑樹的概念:
- 首先紅黑樹是一顆二叉搜索樹
- 每個節點都有顏色,紅色或黑色
- 最長路徑最多是最短路徑的二倍
紅黑樹的性質:
- 每個結點不是紅色就是黑色
- 根節點是黑色的
- 如果一個節點是紅色的
則它的兩個孩子結點是黑色的 - 每條路徑上的黑色節點數相同
- 每個葉子結點都是黑色的
(此處的葉子結點指的是空結點)
為啥滿足了以上性質的紅黑樹就一定
能做到最長路徑最多是最短路徑的二倍?
下面我畫一個極限情況來分析一下!
3. 紅黑樹為什么更實用?
現在將AVL數和紅黑樹做一個對比:
AVL樹闡析:
AVL樹是一顆高度平衡二叉樹,
它的高度差不能大于1,所以AVL
樹的查找是妥妥的O(logn),但是
由于AVL樹嚴格的標準,使得在使用
AVL樹時會經常旋轉,反而增加了時間!
紅黑樹闡析:
紅黑樹是一顆接近平衡的二叉樹
它最長路徑最多是最短路徑的二倍
所以查找的效率是:logn~2logn,
然而2logn和logn是一個量級的,
并且紅黑樹的規則沒有這么嚴格,
不會涉及到更多旋轉和變色!
綜上所述,紅黑樹的效率雖然比
AVL樹差一點,但是總體來說紅黑樹勝!
4. 紅黑樹模擬實現代碼框架
首先,每個節點都要存一個顏色,
這里我們使用枚舉enum來實現
并且和AVL一樣也是三叉鏈!
請看代碼:
enum Colour
{RED,BLACK
};
template<class K,class V>
struct RBTreeNode
{RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv),_col(RED){}RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv; Colour _col;
};template<class K, class V>
struct RBTree
{
typedef RBTreeNode<K, V> Node;
private:Node* _root = nullptr;
};
有了代碼框架后,現在來看看插入
5. 紅黑樹的插入操作初步分析
和AVL樹很相似,紅黑樹的插入
也是分為兩步走:
- 按照二叉搜索樹的規則插入值
- 插入后根據顏色或高度做旋轉或變色
眾所周知啊,第一步很簡單
甚至可以將之前的代碼抄過來:
bool insert(const pair<K, V>& kv)//第一步:按照二叉搜索樹的方式插入值
{if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;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);if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_left = cur;//此時new出來的節點的parent還指向空cur->_parent = parent;///前面的過程和AVL樹一致
}
走到這步后,有個問題,新插入的節點
是選擇插入紅色還是黑色?
對于這個問題,我們要思考兩個點,
一是如果插入的是黑色節點,我們
可能會打破哪個規則?如果是插入
紅色節點又可能會打破哪個規則?
-
插入黑色節點,打破規則四,很難辦
因為每個路徑檢查起來很難!
-
插入紅色節點,打破規則三,比打破
四要好一些,因為只用看父親是否為紅
綜上所述,插入紅色更優!
換句話說,你犯錯肯定寧愿犯輕一點
的錯誤被媽媽打一頓,也不愿意犯很重
的錯直接被家族除名了對吧[doge]
6. 紅黑樹的插入操作詳解(一)
如果插入的節點的父親是黑色節點,
那么正是我們想看見的,不用管它了!
那么如果插入的節點的父親是紅色呢?
很明顯,這違反了規則三,有連續的紅色
節點,所以此時需要做處理了!
先來看一個最簡單的情況:
其實紅黑樹插入節點后的變色和
旋轉規則主要是看叔叔,叔叔的情況
不同,那么對應的處理手段就不同,這里
只通過簡單變色手段就可以滿足規則了!
并且在上圖中,將爺爺變成紅色后可能會
出現問題,因為爺爺的父親不知道是紅色
還是黑色,所以要不斷向上做判斷
若不向上更新,可能會有這種情況:
7. 紅黑樹的插入操作詳解(二)
當講解完最簡單的情況后,還剩下兩種
情況,這兩種情況內部又可細分出幾種
情況,請同學們耐心學習!
情況二:cur為紅.p為紅.g為黑.u不存在/為黑
(并且cur和p都是左或右)
p為g的左孩子,cur為p的左孩子,則右單旋
p為g的右孩子,cur為p的右孩子,則左單旋
p,g變色—p變黑,g變紅
情況三:cur為紅.p為紅.g為黑.u不存在/為黑
(并且若p是g的左,cur就是p的右)
p為g的左孩子,cur為p的右孩子,則做左單旋
p為g的右孩子,cur為p的左孩子,則做右單旋
則轉換成了情況二
至此,紅黑樹的插入的所有情況都
講解完畢,接下來就是代碼實現了!
8. 紅黑樹的插入代碼實現
在整個大情況分類中,可以歸為兩類
一是叔叔為紅色,二是叔叔為黑色或者
叔叔不存在,我們圍繞著這兩個大方向寫!
//走到這一步后,就已經找到cur和parent了!
while (parent && parent->_col == RED)//當parent為黑就不用往上更新了
{if (parent == _root){_root->_col = BLACK;break;}Node* grandf = parent->_parent;assert(grandf);assert(grandf->_col == BLACK);Node* uncle = nullptr;if (parent == grandf->_left)//判斷叔叔在par的左還是右uncle = grandf->_right;else uncle = grandf->_left;if (uncle == nullptr || uncle->_col == BLACK)//uncle為空或為黑有四種情況來變色+旋轉{if (parent == grandf->_left && cur == parent->_left)//左左->右旋+變色{RotateR(grandf);parent->_col = BLACK;grandf->_col = RED;break;}else if (parent == grandf->_right && cur == parent->_right)//右右->左旋+變色{RotateL(grandf);parent->_col = BLACK;grandf->_col = RED;break;}else if (parent == grandf->_left && cur == parent->_right)//左右->先左旋再右旋再變色{RotateL(parent);RotateR(grandf);cur->_col = BLACK;grandf->_col = RED;break;}else if (parent == grandf->_right && cur == parent->_left)//右左->先右旋再左旋再變色{RotateR(parent);RotateL(grandf);cur->_col = BLACK;grandf->_col = RED;break;}}else if (uncle && uncle->_col == RED)//叔叔為紅,直接變色,不旋轉{parent->_col = BLACK;uncle->_col = BLACK;grandf->_col = RED;cur = grandf;parent = cur->_parent;//將parent更新后往上傳!}_root->_col = BLACK;
}
可以發現一個問題,只要是叔叔的顏色
是黑色或叔叔不存在的情況下,執行完
旋轉+變色后都直接break了,這是因為
在這種情況下,父親節點都被變成了黑色,
也就沒必要繼續往上了!并且在紅黑樹的
左旋和右旋中,代碼其實和AVL樹的旋轉是
一模一樣的,所以直接copy一份就行了!
若你不清楚旋轉的代碼,請看這篇文章:
AVL樹模擬實現!
9. 總結以及拓展
AVL樹和紅黑樹的代碼實現都只講解
了插入操作,因為刪除操作太復雜了,
并且就算實現了刪除操作也沒有太大
的實際意義,所以只需要了解插入即可!
并不是需要你真正的會手撕!
拓展閱讀:
紅黑樹的刪除圖解