資料結構高階--紅黑樹(圖解+實現)
紅黑樹
概念和性質
紅黑樹的概念:
紅黑樹,是一種二叉搜尋樹,但在每個結點上增加一個儲存位表示結點的顏色,可以是Red或Black。它是通過控制節點顏色的方式來控制這棵樹的相對平衡,保證了沒有一條路徑會比其它路徑長出兩倍,紅黑樹是一種接近平衡的二叉樹(說它是接近平衡因為它並沒有像AVL樹的平衡因子的概念,它只是靠著滿足紅黑節點的性質來維持一種接近平衡的結構,進而提升整體的效能,並沒有嚴格的卡定某個平衡因子來維持絕對平衡)。
在講解紅黑樹性質之前,先簡單瞭解一下幾個概念:
- parent:父節點
- sibling:兄弟節點
- uncle:叔父節點( parent 的兄弟節點)
- grand:祖父節點( parent 的父節點)
紅黑樹的性質:
-
節點是紅色或黑色
-
根節點是黑色
-
所有葉子節點都是黑色(葉子是空結點),這裡的葉子節點要注意以下,指的是最底層的空節點(外部節點),上圖中的那些NULL節點才是葉子節點,NULL節點的父節點在紅黑樹裡不將其看作葉子節點
-
每個紅色結點的兩個子結點都是黑色
- 紅色節點的父節點都是黑色
- 從根節點到葉子節點的所有路徑上不能有2個連續的紅色節點
-
從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點
思考一個問題: 為什麼紅黑樹中最長路徑的長度不會超過最短路徑節點個數的兩倍?
最長路徑: 該條路徑上節點分佈是一紅一黑
最短路徑: 該條路徑上節點分佈是全黑
假設每條路徑黑色節點數為N,則最長路徑為2N,最短路徑為N,所以這樣就推出紅黑樹中最長路徑的長度不會超過最短路徑節點個數的兩倍
紅黑樹的實現
紅黑樹節點定義
這裡也是一個三叉鏈,其中每個節點包含顏色的元素在裡面
enum Color { RED, BLACK }; template <class K,class V> class RBTree_Node { public: RBTree_Node(const K& key, const V& value, Color color = RED) :left(nullptr), right(nullptr), parent(nullptr), key(key), value(value),color(color) {} public: RBTree_Node<K, V>* left; RBTree_Node<K, V>* right; RBTree_Node<K, V>* parent; K key; V value; Color color; };
紅黑樹結構定義
template <class K,class V>
class RB_Tree
{
typedef RBTree_Node<K,V> Node;
public:
private:
Node* root = nullptr;
};
紅黑樹的插入
方法概述
第一步: 我們先按照二叉搜尋樹樹插入節點的方式,插入節點
第二步: 為了不破壞紅黑樹的規則,我們插入節點後要對紅黑樹進行相應的調整
思考: 我們插入節點應該預設插入紅色節點好還是黑色節點好呢?
答案是紅色節點。為什麼呢?我們要考慮哪種方式對紅黑樹的破壞性更大一點。如果是黑色,此時黑導致該條路徑比其它路徑上黑色節點數都多一個,這樣下來調整紅黑樹的步驟代價似乎會有點大;如果是紅色,不會破壞第五條規則,只是破壞第四條規則,可能會出現連續的紅色節點,這樣我們只需要調整該節點及其附近節點的顏色即可,代價沒有前一種方式大,所以我們選擇第二種方式。
調整節點顏色
第一種情況: 如果插入節點的父親是黑色節點,那麼可以直接結束,不需要繼續調整了
第二種情況: 如果插入節點為的父親為紅色節點,就需要進行相應的調整
下面討論父親節點是祖父節點的左孩子的幾種情形(是右孩子的情形和這個型別,大家可以自己推演一下,這裡我們把父親節點叫做p(parent),祖父叫g(grandfather),叔叔節點叫u(uncle)):
情景1:插入節點cur為紅,p為紅色(若p為紅,那麼g肯定是存在的且為黑),u存在且為紅
前情提要:首先你需要知道,紅黑樹的調整關鍵看叔叔,因為節點的顏色是固定的,祖父存在且一定為黑,為什麼?因為父節點為紅色那麼他一定不會是根,他一定有一個父節點且為黑,只有 u 節點不知道嘛情況。
操作: 把p和u改成黑,g改成紅,如果g為根節點就把g的顏色變成黑然後結束,如果g不為根節點,且g的父親為黑色也結束,為紅色就需要迭代向上調整,判斷此時處於何種情況
為什麼要這麼操作呢?
我們調整紅黑樹不是像AVL樹一樣旋轉,而是進行變色處理,首先要將父親變黑,此時就沒有連續的紅節點了,但是為了保持黑節點數目不變,所以叔叔也要變黑,緊接著還要將祖父變紅,為什麼呢?這可能是一個子樹,叔叔父親變黑了那麼路徑上黑節點就會多出一個,對整個結構必然有影響,所以還需要將祖父變紅保持黑色數目不變
具像圖:
如果g的父親為紅,就迭代向上調整:cur = grandfather,grandfather = grandfather -> parent
抽象圖:
抽象圖中cur可能是新插入的節點,也可能是迭代調整上來的節點,這裡g這棵子樹每條路徑黑色節點數是相同的,且調整後g這棵子樹的每條路徑黑色數相同且和之前一樣。cur是parent的左孩子和右孩子是一樣的,因為這裡都是對顏色進行控制,和方向無關
情景2:插入的cur為紅色, p為紅色(g肯定是存在的且為黑),u不存在
前情提要:在這種情況下的 cur 結點一定是新增節點,因為叔叔不存在,g的右子樹沒有黑節點,那麼 parent 的下面也不可能再掛黑結點了(不然從g到左右子樹路徑上黑節點的數目就不相同了)
既然產生了連續的紅節點那就必須把其中一個變黑,總不能把插進去的紅節點變黑吧,不然就變成插黑節點了,因此就在只能把 parent 變黑,但是這樣子樹就會多出黑節點有會破壞結構,怎麼辦呢?
操作: cur為parent的左孩子時,對g進行右單旋,然後將p的顏色改黑,g的顏色改紅;cur為parent的右孩子時,先對p進行左單旋,然後對g進行右單旋,然後將cur的顏色改黑,g的顏色改紅
cur為parent的左孩子時 一條直線,此時進行右單旋
cur為parent的左孩子時 一條折線,此時進行左右雙旋
抽像圖:
情景3:插入的cur為紅色,p為紅色(g肯定是存在的且為黑),u存在且為黑
操作: 如果cur為parent的左孩子,對g進行右單旋,然後將p的顏色改為黑,g的顏色改為紅;如果cur為parent的右孩子,先對p進行左單旋,對g進行右單旋,然後將cur的顏色改為黑,g的顏色改為紅
解釋: 假設此時a和b中黑色節點數為a,c的黑色節點數也一定為a,d和e的黑色節點數就是a-1,調整後cur和g的抽象圖的黑色節點都是a,整體相等
抽象圖:此時cur一定為調整上來的節點,因為如果是新增節點的話,那麼原來g這棵子樹左右黑色節點數目不等,所以cur一定是調整上來的節點(第一種情況向上調整的結果),祖父節點形成的 cur 節點,具體畫個影象看看吧!
如上圖所示,根據性質 4 假設祖父上面有 x 個黑節點,那麼左子樹(含祖父)現在是 x +1 個,右子樹(含祖父)是 x + 2 + y 個,很明顯 x + 2 + y > x + 1,因此在插入結點前就已經不滿足要求了,所以說叔叔結點存在且為黑這種情況,一定是由情況一往上調整過程中才會出現的!
cur為parent的左孩子 一條直線,進行右單旋即可
cur為parent的右孩子 一條折線,此時進行左右雙旋
總結: 上面就是p是g的左孩子的所有情形,為g的右孩子是與這個類似。還有注意的是根節點最後一定要改為黑色。
插入程式碼實現
旋轉程式碼如下:這就是上一篇部落格的旋轉程式碼
/*
注意:一般選取第一個不平衡的節點作為parent
*/
//左單旋,新插入的節點在右子樹的右側
/*
步驟:
1.讓subR的左孩子成為parent的右孩子
2.然後讓parent成為subR的左孩子
3.最後把兩個節點的平衡因子修改為0
*/
void RotateL(Node* parent)
{
Node* subR = parent->right;
Node* subRL = subR->left;
//1.先把subR左邊(可能為空也可能不為空)作為parent的右邊
parent->right = subRL;
//2.如果subRL不為空,那麼就讓subRL的父指標指向parent
if (subRL)
{
subRL->parent = parent;
}
//3.先記錄parent的父節點的位置,然後把parent作為subR的左邊
Node* ppNode = parent->parent;
subR->left = parent;
//4.parent的父指標指向subR
parent->parent = subR;
//5.如果ppNode為空-->說明subR現在是根節點,就讓subR的父指標指向nullptr
//如果不是根節點就把subR的父指標指向parent的父節點,parent的父節點(左或右)指向subR
if (ppNode == nullptr)
{
//更新根節點
root = subR;
subR->parent = nullptr;
}
else
{
//判斷parent是ppNode的左還是右
if (ppNode->left == parent)
{
ppNode->left = subR;
}
else
{
ppNode->right = subR;
}
subR->parent = ppNode;
}
}
//右單旋,新插入的節點在左子樹的左側
/*
步驟:
1.讓subL的右孩子成為parent的左孩子
2.然後讓parent成為subL的右孩子
3.最後把兩個節點的平衡因子修改為0
*/
void RotateR(Node* parent)
{
Node* subL = parent->left;
Node* subLR = subL->right;
//1.先把subL的右邊(可能為空也可能不為空)作為parent的左邊
parent->left = subLR;
//2.如果subLR不為空,就把subLR的父指標指向parent
if (subLR)
{
subLR->parent = parent;
}
//3.記錄parent的父節點的位置,然後把parent作為subL的右邊
Node* ppNode = parent->parent;
subL->right = parent;
//4.parent的父親指標指向subL
parent->parent = subL;
//5.如果ppNode為空-->說明subL現在是根節點,就讓subL的父節點指向nullptr
//不是根節點就把subL的父節點指向parent的父節點,parent的父節點(左或右)指向subL
if (ppNode == nullptr)
{
//更新根節點
root = subL;
subL->parent = nullptr;
}
else
{
//判斷parent是ppNode的左還是右
if (ppNode->left == parent)
{
ppNode->left = subL;
}
else
{
ppNode->right = subL;
}
subL->parent = ppNode;
}
}
插入程式碼實現如下:
pair<Node*, bool>Insert(const K& key, const V& value)
{
if (root == nullptr)
{
root = new Node(key, value, BLACK);//根節點預設黑
return make_pair(root, true);
}
Node* cur = root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (key < cur->key)
{
cur = cur->left;
}
//大於往右走
else if (key > cur->key)
{
cur = cur->right;
}
else
{
//待插入節點的key值=當前節點
return make_pair(nullptr,false);//插入失敗
}
}
//節點預設給紅節點,帶來的影響最小
cur = new Node(key, value);
Node* newnode = cur; //記錄新插入的結點(便於後序返回)
if (cur->key < parent->key)
{
parent->left = cur;
cur->parent = parent;
}
else
{
parent->right = cur;
cur->parent = parent;
}
//調整顏色
/*
情況1:p為紅,g為黑,u存在且為紅
操作:將p和u改為黑,g改為紅
調整後的幾種情況:
1.如果g是根節點,把g改成黑,結束
2.如果g不是根節點
a.g的父節點為黑,結束
b.g的父節點為紅,迭代向上調整,繼續判斷是那種情況(1和3)
cur = grandfather; father = cur->father
這裡不關心cur是在p的左邊還是右邊,都是一樣的,關心的是顏色而不是位置
情況2:p為紅,g為黑,u不存在/u為黑 cur p g三個一條直線
操作(左邊為例):1.右單旋 2.把p改成黑,g改成紅
1.u不存在時,cur必定是新增節點
2.u存在時,cur必定是更新上來的節點
情況3:p為紅,g為黑,u不存在/u為黑 cur p g三個是一條折線
操作(左邊為例):1.p左單旋 2.g右單旋 3.把cur改成黑,g改成紅
1.u不存在時,cur必定是新增節點
2.u存在時,cur必定是更新上來的節點
*/
//若插入節點的父節點是紅色,則需要對紅黑樹進行調整
while (parent && parent->color == RED)
{
//父節點是紅色,那麼其父節點一定存在
Node* grandfather = parent->parent;
if (parent == parent->left)
{
//叔叔節點,紅黑樹調整的關鍵就是叔叔節點
Node* uncle = grandfather->right;
//情況1:u存在並且為紅
if (uncle && uncle->color == RED)
{
//顏色調整
parent->color = uncle->color = BLACK;
grandfather->color = RED;
//迭代處理
}
else//情況2+情況3 uncle不存在+uncle為黑
{
//折線處理:p左單旋,g右單旋,把cur改成黑,g改成紅
if (cur == parent->right)
{
RotateL(parent);
swap(parent, cur);
}
//直線cur p g 把p改成黑,g改成紅
//右單旋 有可能是第三種情況
RotateR(grandfather);
parent->color = BLACK;
grandfather->color = RED;
}
}
//uncle在左邊,parent是grandfather的右孩子
else
{
Node* uncle = grandfather->left;
if (uncle && uncle->color == RED)
{
parent->color = uncle->color = BLACK;
grandfather->color = RED;
// 迭代 向上調整
cur = grandfather;
parent = cur->parent;
}
else
{
// 折線用一個右單旋處理 g p cur g變紅p邊黑
if (cur == parent->left)
{
RotateR(parent);
swap(parent, cur);
}
// 直線 g p cur 把p改成黑,g改成紅
// 左單旋 有可能是第三種情況
RotateL(grandfather);
parent->color = BLACK;
grandfather->color = RED;
}
}
}
root->color = BLACK; //暴力處理根節點顏色,防止向上處理根節點變紅
return make_pair(cur,true);
}
紅黑樹的刪除
方法概述
第一步: 先按照二叉搜尋樹刪除節點的方式找到要刪除節點(也可能是替代節點)
第二步: 然後為了不破壞紅黑樹的幾條規則,要對節點的顏色進行相應地調整
調整節點顏色
在紅黑樹中,刪除一個節點,只有以下四種情況:
情況一:刪除的節點的左、右子樹都非空
情況二:刪除的節點的左子樹為空樹,右子樹非空
情況三:刪除的節點的右子樹為空樹,左子樹非空
情況四:刪除的節點的左、右子樹都為空樹
其中情況一,可以按與其他二叉搜尋樹的刪除方式一樣處理,最終可以轉換到後面的三種情況,在二叉搜尋樹刪除中我有介紹,當左右節點都不為空的時候,最終會轉化為情況二三四,所以我們只需要討論後三種情況就可以了
第一種情況:刪除節點(也可能是替代節點)(之後都叫delNode),如果該節點為紅色,則直接刪除退出即可,delNode沒找到也可以直接退出
注意delNode的左節點之所以為NULL,不是因為我們沒考慮delNode左孩子存在的情況,而是左右都存在的情況最終都會轉化為只有一棵子樹的形況,參考二叉搜尋樹的刪除節點
這裡的DR存在兩種情況,為空或存在右子樹,在後面會對DR進行詳細的討論
分析一下:因為delNode是紅色,所以P必定為黑色,同時DR不可能為紅色,根據紅黑樹的性質,DR必定為NULL,若DR存在節點,那麼下面必定還有有NULL節點,delNode的左右節點數目不對,所以此時DR一定是NULL,現在要刪除delNode節點,只需要直接將delNode刪除,並且把DR作為P的左孩子即可,相信大家都明白了當要刪除節點為紅色的時候,為什麼直接刪除就可以了吧
第二種情況:delNode為黑色且delNode只有一個孩子時(此時該孩子一定為紅色,不可能是黑色),刪除delNode節點,然後把孩子節點的顏色改成黑色,也可直接結束
分析一下:若delNode是黑色,並且只有一個孩子DR時,此時就要分析DR為空還是不為空,若DR不為空,根據性質,那麼DR必定為紅色,且DR的兩個子節點必定為NULL,此時刪除的delNode是黑色,刪除後P的左子樹的黑節點數必定少1,剛好此時DR為黑色,並且刪除後DR可以佔據delNode的位置(此時仍是一棵二叉搜尋樹,只是暫時還是不是合格的紅黑樹),然後再將DR的顏色改為黑色,剛好可以填補P左子樹所減少的黑節點數,從而達到紅黑樹的平衡
第三種情況:delNode為黑色,且沒有孩子時,有下面幾種情況(兄弟節點叫b(brother),父親節點叫p(parent)):
情況1:delNode為黑,b為紅,則兩個孩子一定為黑
如果DR為NULL,刪除delNode後,p的左子樹黑節點數必定少1,純粹想在p的左子樹做文章來平衡p樹是絕無可能的了。因此,必定需要其他分支的輔助來最終完成平衡調整。根據紅黑樹的定義,p會有一個右子節點,稱為b子節點。此處又可細節分兩種情況:黑b與紅b。此處先討論紅b的情況。
操作:對p進行左單旋,然後將p的顏色改成紅,b的顏色改成黑
分析一下:刪除前P樹的左右子樹的黑節點數平衡,刪除後(上圖右側所示),經過DR分支的黑節點數將比通過b分支的黑節點數少1。此時進行下面操作
將p左單旋,再將p由黑色改成紅色,將b由紅色改成黑色,讓我們看看演變的過程:
經過上邊的變化之後,經過p的路徑,左分支黑節點數仍是少1(因為此時DR為NULL,而bL不是NULL,下面還有一個黑色的NULL,所以左分支比右分支少1),其他分支的黑節點數仍然保持不變。此時的情況卻變成DR的兄弟節點為黑色(不再是紅色的情況了),此時就回到了一開始所說的另一種情況(b為黑色的情況)的處理。
提示:此時需要把 bL 節點看成原先的 b節點
注意:可能有人會一時想不明白什麼要這樣轉換。因為這樣轉換後,雖然對於p樹的左子樹的黑節點數仍然會比右子樹的黑節點數少1,但此時DR的兄弟(以前的b節點)現在已經變為bL,即已經由紅色變為黑色,並且非常重要的此時的DR的兄弟節點bL的子結點(即:DR的兩個侄子節點),要不就是紅色節點要不就必為NULL節點,而這種情況正是delNode為黑色、b也黑色的情況。(注意看注意看注意看一定注意看這點:對於被刪除節點delNode的父節點來說,delNode黑b黑的情況下,無論如何delNode的兄弟節點b的兩個兒子節點dL與dR都不可能是非NULL的黑節點)。因此實際上,這種轉換就是把delNode為黑,b為紅這種情況,轉化為下面情況2中的情景三和情景四進行分析。
情況2:被刪除的delNode為黑色,b也為黑色的情況(分四種情景討論)
情景一:bL為紅,bR顏色任意(對於該情況的處理,其實我們不關心P的顏色)
操作: 先對b進行右單旋,然後把b改紅,bL改黑,然後對p進行左單旋,然後交換p和b的顏色,並把b的顏色改成黑
如圖當對b進行右單旋,將bL變黑,b改紅之後,實際上變成了下面的情景二,接下來執行情景二的操作就可以了
分析一下:此時p樹的左子樹黑節點樹減少1,因此,想要平衡,必需想辦法讓左子樹的黑結節數增加1,而且同時P的右子樹的黑節點數要保持不變。因此,想辦法將bL這個紅色節點利用起來,讓它來填補p的左子樹所缺少的黑節點個數。因此,立馬想到旋轉,只要有辦法轉到p的左子樹或p位置上,則就有可能填平p左子樹的高度。
情景二:bR為紅,bL顏色任意(對於該情況的處理,其實我們不關心P的顏色)
操作: 對p進行左單旋,然後交換p和b的顏色,並把bR的顏色改成黑
情景三:bL、bR都為黑(其實都是NULL節點);p為紅。(注意:這種情況下,實際上就是delNode為黑,b為紅轉換之後的情況,此時b不為NULL,而bL與bR必定必定都為NULL節點)
操作:把p的顏色改成黑,b的顏色改成紅
分析一下:此情況較為簡單,直接將紅色的p改為黑色,以此為填補DR缺少的黑節點個數。此時p右子樹黑節點數卻增多,因此,再將b改為紅色即可平衡
情景四:bL、bR都為黑(其實都是NULL節點);p為黑。(注意:這種情況下,實際上就是delNode為黑,b為紅轉換之後的情況,此時b不為NULL,而bL與bR必定必定都為NULL節點)
操作:把b的顏色改為紅
分析一下:因為DR、p、b、bL、bR全都為黑色,則不論如何變換,都永遠不可能使用p的左右子樹的黑節點數達到平衡。而p不平衡的原因是因為P的右子樹黑節點數會比左子樹多1個。因此,乾脆將b由黑色改為紅色,如此一來,p的左、右子樹的黑節點個數是平衡的。但是,此時經過p的路徑的黑節點個數將會比不經過它的路徑少一個。因此,我們將p作為待平衡的節點(即:此時的p相當於DR的角色)往上繼續上溯,直到p為根節點為止。
思考一下:為什麼要向上回溯?前面的幾種情況為什麼不用回溯
答案很簡單,因為只有這種情況下,p這棵樹整體黑節點數目-1了,試想一下,若p的父親g,假設p是g的右子樹,在沒有刪除delNode節點之前,g到左右子樹的黑節點數目都是相同的,但是如果刪除了delNode,p這棵樹的黑節點整體-1,那麼g的左右子樹就不再平衡,g經過p和不經過p的黑節點數目始終相差1,所以需要向上回溯調整
總結: 刪除就是以上幾種情況,一般是左邊少一個黑色節點,就靠右邊補一個,結束,或者右邊減少一個,然後兩邊整體少一個,對父親節點進行檢索。
刪除程式碼實現
bool Erase(const K& key)
{
//如果樹為空,刪除失敗
if (root == nullptr)
{
return false;
}
Node* parent = nullptr;
Node* cur = root;
Node* delNode = nullptr;
Node* delNodeParent = nullptr;
while (cur)
{
//小於往左走
if (key < cur->key)
{
parent = cur;
cur = cur->left;
}
//大於往右走
else if (key > cur->key)
{
parent = cur;
cur = cur->right;
}
else
{
//找到了,當前要刪除的節點是cur
// 1.左右子樹都為空 直接刪除 可以歸類為左為空
// 2.左右子樹只有一邊為空 左為空,父親指向我的右,右為空,父親指向我的左
// 3.左右子樹都不為空 取左子樹最大的節點或右子樹最小的節點和要刪除的節點交換,然後再刪除
if (cur->left == nullptr)
{
//要刪除的節點為根節點的時候,直接把右子樹的根節點賦值給root
//根節點的話會導致parent為nullptr
if (root == cur)
{
root = root->right;
if (root)
{
root->parent = nullptr;
//根節點都是黑節點
root->color = BLACK;
}
return true;
}
else
{
delNode = cur;
delNodeParent = parent;
}
}
else if (cur->right == nullptr)
{
if (root == cur)
{
root = root->left;
if (root)
{
root->parent = nullptr;
//根節點都是黑節點
root->color = BLACK;
}
return true;
}
else
{
delNode = cur;
delNodeParent = parent;
}
}
else
{
//此時是左右子樹都不為空的情況,要進行轉化
//找到右子樹中最小的節點
Node* rightMinParent = cur;
Node* rightMin = cur->right;//去右子樹去找
while (rightMin->left)
{
rightMinParent = rightMin;
rightMin = rightMin->left;
}
cur->key = rightMin->key;//替代,但是不刪除,只是利用了二叉搜尋樹的替換
//替換的是右子樹最左邊的節點,一定不存在左子樹,是不是就將左右子樹都存在的情況進行了轉化
delNode = rightMin;
delNodeParent = rightMinParent;
}
break;
}
}
//沒找到
if (cur == nullptr)
{
return false;
}
/*
1.替代節點為紅,直接刪除
2.替代節點為黑
a.左右子樹都存在,這種情況可以轉化為b,上面介紹過了
b.只有一棵子樹存在(該子樹節點一定為紅),把孩子的顏色改成黑
c.左右子樹都不存在
*/
cur = delNode;
parent = delNodeParent;
if (cur->color == BLACK)
{
if (cur->left)//左孩子不為空
{
cur->left->color = BLACK;
}
else if (cur->right)
{
cur->right->color = BLACK;
}
else//替換的節點兩個孩子都是空
{
while (parent)
{
//cur是parent的左
if (cur == parent->left)
{
Node* brother = parent->right;
//p為黑
if (parent->color == BLACK)
{
Node* bL = brother->left;
Node* bR = brother->right;
if (brother->color == RED)//b為紅,那麼bL和bR一定是黑
{
//對p進行左單旋,然後將p的顏色改成紅,b的顏色改成黑
RotateL(parent);
brother->color = BLACK;
parent->color = RED;
//還沒結束,此時只是將b為紅的情況轉化為b為黑的情況,還需要繼續檢索
}
//b為黑,孩子都為黑,情景四
else if (bL && bR && bL->color == BLACK && bR->color == BLACK)
{
//把b的顏色改為紅,繼續向上回溯
brother->color = RED;
cur = parent;
parent = parent->parent;
}
//b為黑,bR為紅,情景二
else if (bR && bR->color == RED)
{
// 對p進行左單旋,然後交換p和b的顏色,並把bR的顏色改成黑
RotateL(parent);
swap(brother->color, parent->color);
bR->color = BLACK;
break;
}
//b為黑,bL為紅,情景一
else if (bL && bL->color == RED && (!bR || (bR && bR->color == BLACK)))
{
//b進行右單旋,將bL變黑,b改紅之後,實際上變成了下面的情景二,接下來執行情景二的操作就可以了
RotateR(brother);
bL->color = BLACK;
brother->color = RED;
}
//還剩一個情景三
else
{
//操作:把p的顏色改成黑,b的顏色改成紅
//cur p b都是黑,且b無孩子,迭代更新,parent為紅就結束
brother->color = RED;
cur = parent;
parent = parent->parent;
}
}
//p為紅,b一定是黑
else
{
Node* bL = brother->left;
Node* bR = brother->right;
// b的孩子全為黑 情景三 p變黑,b變紅 結束
if (bL && bR && bL->color == BLACK && bR->color == BLACK)
{
brother->color = RED;
parent->color = BLACK;
break;
}
// bL存在為紅,bR不存在或bR為黑 情景一 右旋後變色轉為情景一
else if (bL && bL->color == RED && (!bR || (bR && bR->color == BLACK)))
{
RotateR(brother);
bL->color = BLACK;
brother->color = RED;
}
//bR存在為紅,進行一個左旋,然後把右孩子的顏色改成黑色 情景二
else if (bR && bR->color == RED)
{
RotateL(parent);
brother->color = parent->color;
parent->color = BLACK;
bR->color = BLACK;
break;
}
else// cur 為黑,p為紅,b為黑 調整顏色,結束
{
parent->color = BLACK;
brother->color = RED;
break;
}
}
}
//cur是右子樹的情況,和左正好反過來
else
{
Node* brother = parent->left;
// p為黑
if (parent->color == BLACK)
{
Node* bL = brother->left;
Node* bR = brother->right;
// SL和SR一定存在且為黑
if (brother->color == RED)// b為紅,SL和SR都為黑 b的顏色改黑,p的顏色改紅 情況a
{
RotateR(parent);
brother->color = BLACK;
parent->color = RED;
// 沒有結束,還要對cur進行檢索
}
else if (bL && bR && bL->color == BLACK && bR->color == BLACK)// b為黑,孩子存在
{
// 且孩子也為黑 把brother改成紅色,迭代 GP比GU小1 情況b
brother->color = RED;
cur = parent;
parent = parent->parent;
}
// 右孩子存在且為紅,但左孩子不存在或為黑 情況e 右旋後變色轉為情況d
else if (bR && bR->color == RED && (!bL || (bL && bL->color == BLACK)))
{
RotateL(brother);
brother->color = RED;
bR->color = BLACK;
}
else if (bL && bL->color == RED) // 左孩子為紅,進行一個右旋,然後把左孩子的顏色改成黑色 情況d
{
RotateR(parent);
swap(brother->color, parent->color);
bL->color = BLACK;
break;
}
else
{
// cur p b 都是黑,且b無孩子,迭代更新
// if (parent == _root) // p是根節點,把b變紅 否則迭代
brother->color = RED;
cur = parent;
parent = parent->parent;
}
}
// p為紅 b一定為黑
else
{
Node* bL = brother->left;
Node* bR = brother->right;
if (bL && bR && bL->color == BLACK && bR->color == BLACK)// b的孩子全為黑 情況c p變黑,b變紅 結束
{
brother->color = RED;
parent->color = BLACK;
break;
}
// 右孩子存在且為紅,但左孩子不存在或為黑 情況e 右旋後變色轉為情況d
else if (bR && bR->color == RED && (!bL || (bL && bL->color == BLACK)))
{
RotateL(brother);
brother->color = RED;
bR->color = BLACK;
}
else if (bL && bL->color == RED) // 左孩子為紅,進行一個右旋,然後把左孩子的顏色改成黑色 情況d
{
RotateR(parent);
// swap(brother->color, parent->color);
brother->color = parent->color;
parent->color = BLACK;
bL->color = BLACK;
break;
}
else// cur 為黑,p為紅,b為黑 調整顏色,結束
{
parent->color = BLACK;
brother->color = RED;
break;
}
}
}
}
}
}
//上面是對顏色進行了調整,接下來進行刪除操作
delNodeParent = delNode->parent;
//刪除
if (delNode->left == nullptr)
{
if (delNodeParent->left == delNode)
{
delNodeParent->left = delNode->right;
}
else
delNodeParent->right = delNode->right;
if (delNode->right)// 右不為空,就讓右節點的父指標指向delNodeParent
delNode->right->parent = delNodeParent;
}
else
{
if (delNodeParent->left == delNode)
delNodeParent->left = delNode->left;
else
delNodeParent->right = delNode->left;
if (delNode->left)// 右不為空,就讓右節點的父指標指向delNodeParent
delNode->left->parent = delNodeParent;
}
delete delNode;
delNode = nullptr;
return true;
}
紅黑樹的查詢
//紅黑樹的查詢
bool Find(const K& key)
{
if (root == nullptr)
return false;
Node* cur = root;
while (cur)
{
// 小於往左走
if (key < cur->key)
{
cur = cur->left;
}
// 大於往右走
else if (key > cur->key)
{
cur = cur->right;
}
else
{
// 找到了
return true;
}
}
return false;
}
紅黑樹遍歷
//中序遍歷(遞迴)
void InOrder()
{
_InOrder(root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == NULL)
{
return;
}
else
{
_InOrder(root->left);
cout << root->key << ":" << root->value << " ";
_InOrder(root->right);
}
}
紅黑樹的驗證
//紅黑樹的驗證
bool IsValidRBTree()
{
// 空樹也是紅黑樹
if (root == nullptr)
return true;
// 判斷根節點的顏色是否為黑色
if (root->color != BLACK)
{
cout << "違反紅黑樹的根節點為黑色的規則" << endl;
return false;
}
// 計算出任意一條路徑的黑色節點個數
size_t blackCount = 0;
Node* cur = root;
while (cur)
{
if (cur->color == BLACK)
++blackCount;
cur = cur->left;
}
// 檢測每條路徑黑節點個數是否相同 第二個引數記錄路徑中黑節點的個數
return _IsValidRBTree(root, 0, blackCount);
}
bool _IsValidRBTree(Node* root, size_t k, size_t blackCount)
{
// 走到空就判斷該條路徑的黑節點是否等於blackCount
if (root == nullptr)
{
if (k != blackCount)
{
cout << "違反每條路徑黑節點個數相同的規則" << endl;
return false;
}
return true;
}
if (root->color == BLACK)
++k;
// 判斷是否出現了連續兩個紅色節點
Node* parent = root->parent;
if (parent && root->color == RED && parent->color == RED)
{
cout << "違反了不能出現連續兩個紅色節點的規則" << endl;
return false;
}
return _IsValidRBTree(root->left, k, blackCount)
&& _IsValidRBTree(root->right, k, blackCount);
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->left);
int rightHeight = _Height(root->right);
return 1 + max(leftHeight, rightHeight);
}
紅黑樹完整程式碼及測試
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入標頭檔案
#include<string>//C++中的字串
#include<vector>
using namespace std; //標準名稱空間
enum Color
{
RED, BLACK
};
template <class K,class V>
class RBTree_Node
{
public:
RBTree_Node(const K& key, const V& value, Color color = RED) :left(nullptr), right(nullptr), parent(nullptr),
key(key), value(value), color(color)
{}
public:
RBTree_Node<K, V>* left;
RBTree_Node<K, V>* right;
RBTree_Node<K, V>* parent;
K key;
V value;
Color color;
};
template <class K,class V>
class RB_Tree
{
typedef RBTree_Node<K,V> Node;
public:
/*
注意:一般選取第一個不平衡的節點作為parent
*/
//左單旋,新插入的節點在右子樹的右側
/*
步驟:
1.讓subR的左孩子成為parent的右孩子
2.然後讓parent成為subR的左孩子
3.最後把兩個節點的平衡因子修改為0
*/
void RotateL(Node* parent)
{
Node* subR = parent->right;
Node* subRL = subR->left;
//1.先把subR左邊(可能為空也可能不為空)作為parent的右邊
parent->right = subRL;
//2.如果subRL不為空,那麼就讓subRL的父指標指向parent
if (subRL)
{
subRL->parent = parent;
}
//3.先記錄parent的父節點的位置,然後把parent作為subR的左邊
Node* ppNode = parent->parent;
subR->left = parent;
//4.parent的父指標指向subR
parent->parent = subR;
//5.如果ppNode為空-->說明subR現在是根節點,就讓subR的父指標指向nullptr
//如果不是根節點就把subR的父指標指向parent的父節點,parent的父節點(左或右)指向subR
if (ppNode == nullptr)
{
//更新根節點
root = subR;
subR->parent = nullptr;
}
else
{
//判斷parent是ppNode的左還是右
if (ppNode->left == parent)
{
ppNode->left = subR;
}
else
{
ppNode->right = subR;
}
subR->parent = ppNode;
}
}
//右單旋,新插入的節點在左子樹的左側
/*
步驟:
1.讓subL的右孩子成為parent的左孩子
2.然後讓parent成為subL的右孩子
3.最後把兩個節點的平衡因子修改為0
*/
void RotateR(Node* parent)
{
Node* subL = parent->left;
Node* subLR = subL->right;
//1.先把subL的右邊(可能為空也可能不為空)作為parent的左邊
parent->left = subLR;
//2.如果subLR不為空,就把subLR的父指標指向parent
if (subLR)
{
subLR->parent = parent;
}
//3.記錄parent的父節點的位置,然後把parent作為subL的右邊
Node* ppNode = parent->parent;
subL->right = parent;
//4.parent的父親指標指向subL
parent->parent = subL;
//5.如果ppNode為空-->說明subL現在是根節點,就讓subL的父節點指向nullptr
//不是根節點就把subL的父節點指向parent的父節點,parent的父節點(左或右)指向subL
if (ppNode == nullptr)
{
//更新根節點
root = subL;
subL->parent = nullptr;
}
else
{
//判斷parent是ppNode的左還是右
if (ppNode->left == parent)
{
ppNode->left = subL;
}
else
{
ppNode->right = subL;
}
subL->parent = ppNode;
}
}
pair<Node*, bool>Insert(const K& key, const V& value)
{
if (root == nullptr)
{
root = new Node(key, value, BLACK);//根節點預設黑
return make_pair(root, true);
}
Node* cur = root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (key < cur->key)
{
cur = cur->left;
}
//大於往右走
else if (key > cur->key)
{
cur = cur->right;
}
else
{
//待插入節點的key值=當前節點
return make_pair(nullptr,false);//插入失敗
}
}
//節點預設給紅節點,帶來的影響最小
cur = new Node(key, value);
Node* newnode = cur; //記錄新插入的結點(便於後序返回)
if (cur->key < parent->key)
{
parent->left = cur;
cur->parent = parent;
}
else
{
parent->right = cur;
cur->parent = parent;
}
//調整顏色
/*
情況1:p為紅,g為黑,u存在且為紅
操作:將p和u改為黑,g改為紅
調整後的幾種情況:
1.如果g是根節點,把g改成黑,結束
2.如果g不是根節點
a.g的父節點為黑,結束
b.g的父節點為紅,迭代向上調整,繼續判斷是那種情況(1和3)
cur = grandfather; father = cur->father
這裡不關心cur是在p的左邊還是右邊,都是一樣的,關心的是顏色而不是位置
情況2:p為紅,g為黑,u不存在/u為黑 cur p g三個一條直線
操作(左邊為例):1.右單旋 2.把p改成黑,g改成紅
1.u不存在時,cur必定是新增節點
2.u存在時,cur必定是更新上來的節點
情況3:p為紅,g為黑,u不存在/u為黑 cur p g三個是一條折線
操作(左邊為例):1.p左單旋 2.g右單旋 3.把cur改成黑,g改成紅
1.u不存在時,cur必定是新增節點
2.u存在時,cur必定是更新上來的節點
*/
//若插入節點的父節點是紅色,則需要對紅黑樹進行調整
while (parent && parent->color == RED)
{
//父節點是紅色,那麼其父節點一定存在
Node* grandfather = parent->parent;
if (parent == parent->left)
{
//叔叔節點,紅黑樹調整的關鍵就是叔叔節點
Node* uncle = grandfather->right;
//情況1:u存在並且為紅
if (uncle && uncle->color == RED)
{
//顏色調整
parent->color = uncle->color = BLACK;
grandfather->color = RED;
//迭代處理
}
else//情況2+情況3 uncle不存在+uncle為黑
{
//折線處理:p左單旋,g右單旋,把cur改成黑,g改成紅
if (cur == parent->right)
{
RotateL(parent);
swap(parent, cur);
}
//直線cur p g 把p改成黑,g改成紅
//右單旋 有可能是第三種情況
RotateR(grandfather);
parent->color = BLACK;
grandfather->color = RED;
}
}
//uncle在左邊,parent是grandfather的右孩子
else
{
Node* uncle = grandfather->left;
if (uncle && uncle->color == RED)
{
parent->color = uncle->color = BLACK;
grandfather->color = RED;
// 迭代 向上調整
cur = grandfather;
parent = cur->parent;
}
else
{
// 折線用一個右單旋處理 g p cur g變紅p邊黑
if (cur == parent->left)
{
RotateR(parent);
swap(parent, cur);
}
// 直線 g p cur 把p改成黑,g改成紅
// 左單旋 有可能是第三種情況
RotateL(grandfather);
parent->color = BLACK;
grandfather->color = RED;
}
}
}
root->color = BLACK; //暴力處理根節點顏色,防止向上處理根節點變紅
return make_pair(cur,true);
}
bool Erase(const K& key)
{
//如果樹為空,刪除失敗
if (root == nullptr)
{
return false;
}
Node* parent = nullptr;
Node* cur = root;
Node* delNode = nullptr;
Node* delNodeParent = nullptr;
while (cur)
{
//小於往左走
if (key < cur->key)
{
parent = cur;
cur = cur->left;
}
//大於往右走
else if (key > cur->key)
{
parent = cur;
cur = cur->right;
}
else
{
//找到了,當前要刪除的節點是cur
// 1.左右子樹都為空 直接刪除 可以歸類為左為空
// 2.左右子樹只有一邊為空 左為空,父親指向我的右,右為空,父親指向我的左
// 3.左右子樹都不為空 取左子樹最大的節點或右子樹最小的節點和要刪除的節點交換,然後再刪除
if (cur->left == nullptr)
{
//要刪除的節點為根節點的時候,直接把右子樹的根節點賦值給root
//根節點的話會導致parent為nullptr
if (root == cur)
{
root = root->right;
if (root)
{
root->parent = nullptr;
//根節點都是黑節點
root->color = BLACK;
}
return true;
}
else
{
delNode = cur;
delNodeParent = parent;
}
}
else if (cur->right == nullptr)
{
if (root == cur)
{
root = root->left;
if (root)
{
root->parent = nullptr;
//根節點都是黑節點
root->color = BLACK;
}
return true;
}
else
{
delNode = cur;
delNodeParent = parent;
}
}
else
{
//此時是左右子樹都不為空的情況,要進行轉化
//找到右子樹中最小的節點
Node* rightMinParent = cur;
Node* rightMin = cur->right;//去右子樹去找
while (rightMin->left)
{
rightMinParent = rightMin;
rightMin = rightMin->left;
}
cur->key = rightMin->key;//替代,但是不刪除,只是利用了二叉搜尋樹的替換
//替換的是右子樹最左邊的節點,一定不存在左子樹,是不是就將左右子樹都存在的情況進行了轉化
delNode = rightMin;
delNodeParent = rightMinParent;
}
break;
}
}
//沒找到
if (cur == nullptr)
{
return false;
}
/*
1.替代節點為紅,直接刪除
2.替代節點為黑
a.左右子樹都存在,這種情況可以轉化為b,上面介紹過了
b.只有一棵子樹存在(該子樹節點一定為紅),把孩子的顏色改成黑
c.左右子樹都不存在
*/
cur = delNode;
parent = delNodeParent;
if (cur->color == BLACK)
{
if (cur->left)//左孩子不為空
{
cur->left->color = BLACK;
}
else if (cur->right)
{
cur->right->color = BLACK;
}
else//替換的節點兩個孩子都是空
{
while (parent)
{
//cur是parent的左
if (cur == parent->left)
{
Node* brother = parent->right;
//p為黑
if (parent->color == BLACK)
{
Node* bL = brother->left;
Node* bR = brother->right;
if (brother->color == RED)//b為紅,那麼bL和bR一定是黑
{
//對p進行左單旋,然後將p的顏色改成紅,b的顏色改成黑
RotateL(parent);
brother->color = BLACK;
parent->color = RED;
//還沒結束,此時只是將b為紅的情況轉化為b為黑的情況,還需要繼續檢索
}
//b為黑,孩子都為黑,情景四
else if (bL && bR && bL->color == BLACK && bR->color == BLACK)
{
//把b的顏色改為紅,繼續向上回溯
brother->color = RED;
cur = parent;
parent = parent->parent;
}
//b為黑,bR為紅,情景二
else if (bR && bR->color == RED)
{
// 對p進行左單旋,然後交換p和b的顏色,並把bR的顏色改成黑
RotateL(parent);
swap(brother->color, parent->color);
bR->color = BLACK;
break;
}
//b為黑,bL為紅,情景一
else if (bL && bL->color == RED && (!bR || (bR && bR->color == BLACK)))
{
//b進行右單旋,將bL變黑,b改紅之後,實際上變成了下面的情景二,接下來執行情景二的操作就可以了
RotateR(brother);
bL->color = BLACK;
brother->color = RED;
}
//還剩一個情景三
else
{
//操作:把p的顏色改成黑,b的顏色改成紅
//cur p b都是黑,且b無孩子,迭代更新,parent為紅就結束
brother->color = RED;
cur = parent;
parent = parent->parent;
}
}
//p為紅,b一定是黑
else
{
Node* bL = brother->left;
Node* bR = brother->right;
// b的孩子全為黑 情景三 p變黑,b變紅 結束
if (bL && bR && bL->color == BLACK && bR->color == BLACK)
{
brother->color = RED;
parent->color = BLACK;
break;
}
// bL存在為紅,bR不存在或bR為黑 情景一 右旋後變色轉為情景一
else if (bL && bL->color == RED && (!bR || (bR && bR->color == BLACK)))
{
RotateR(brother);
bL->color = BLACK;
brother->color = RED;
}
//bR存在為紅,進行一個左旋,然後把右孩子的顏色改成黑色 情景二
else if (bR && bR->color == RED)
{
RotateL(parent);
brother->color = parent->color;
parent->color = BLACK;
bR->color = BLACK;
break;
}
else// cur 為黑,p為紅,b為黑 調整顏色,結束
{
parent->color = BLACK;
brother->color = RED;
break;
}
}
}
//cur是右子樹的情況,和左正好反過來
else
{
Node* brother = parent->left;
// p為黑
if (parent->color == BLACK)
{
Node* bL = brother->left;
Node* bR = brother->right;
// SL和SR一定存在且為黑
if (brother->color == RED)// b為紅,SL和SR都為黑 b的顏色改黑,p的顏色改紅 情況a
{
RotateR(parent);
brother->color = BLACK;
parent->color = RED;
// 沒有結束,還要對cur進行檢索
}
else if (bL && bR && bL->color == BLACK && bR->color == BLACK)// b為黑,孩子存在
{
// 且孩子也為黑 把brother改成紅色,迭代 GP比GU小1 情況b
brother->color = RED;
cur = parent;
parent = parent->parent;
}
// 右孩子存在且為紅,但左孩子不存在或為黑 情況e 右旋後變色轉為情況d
else if (bR && bR->color == RED && (!bL || (bL && bL->color == BLACK)))
{
RotateL(brother);
brother->color = RED;
bR->color = BLACK;
}
else if (bL && bL->color == RED) // 左孩子為紅,進行一個右旋,然後把左孩子的顏色改成黑色 情況d
{
RotateR(parent);
swap(brother->color, parent->color);
bL->color = BLACK;
break;
}
else
{
// cur p b 都是黑,且b無孩子,迭代更新
// if (parent == _root) // p是根節點,把b變紅 否則迭代
brother->color = RED;
cur = parent;
parent = parent->parent;
}
}
// p為紅 b一定為黑
else
{
Node* bL = brother->left;
Node* bR = brother->right;
if (bL && bR && bL->color == BLACK && bR->color == BLACK)// b的孩子全為黑 情況c p變黑,b變紅 結束
{
brother->color = RED;
parent->color = BLACK;
break;
}
// 右孩子存在且為紅,但左孩子不存在或為黑 情況e 右旋後變色轉為情況d
else if (bR && bR->color == RED && (!bL || (bL && bL->color == BLACK)))
{
RotateL(brother);
brother->color = RED;
bR->color = BLACK;
}
else if (bL && bL->color == RED) // 左孩子為紅,進行一個右旋,然後把左孩子的顏色改成黑色 情況d
{
RotateR(parent);
// swap(brother->color, parent->color);
brother->color = parent->color;
parent->color = BLACK;
bL->color = BLACK;
break;
}
else// cur 為黑,p為紅,b為黑 調整顏色,結束
{
parent->color = BLACK;
brother->color = RED;
break;
}
}
}
}
}
}
//上面是對顏色進行了調整,接下來進行刪除操作
delNodeParent = delNode->parent;
//刪除
if (delNode->left == nullptr)
{
if (delNodeParent->left == delNode)
{
delNodeParent->left = delNode->right;
}
else
delNodeParent->right = delNode->right;
if (delNode->right)// 右不為空,就讓右節點的父指標指向delNodeParent
delNode->right->parent = delNodeParent;
}
else
{
if (delNodeParent->left == delNode)
delNodeParent->left = delNode->left;
else
delNodeParent->right = delNode->left;
if (delNode->left)// 右不為空,就讓右節點的父指標指向delNodeParent
delNode->left->parent = delNodeParent;
}
delete delNode;
delNode = nullptr;
return true;
}
//紅黑樹的查詢
bool Find(const K& key)
{
if (root == nullptr)
return false;
Node* cur = root;
while (cur)
{
// 小於往左走
if (key < cur->key)
{
cur = cur->left;
}
// 大於往右走
else if (key > cur->key)
{
cur = cur->right;
}
else
{
// 找到了
return true;
}
}
return false;
}
//紅黑樹的驗證
bool IsValidRBTree()
{
// 空樹也是紅黑樹
if (root == nullptr)
return true;
// 判斷根節點的顏色是否為黑色
if (root->color != BLACK)
{
cout << "違反紅黑樹的根節點為黑色的規則" << endl;
return false;
}
// 計算出任意一條路徑的黑色節點個數
size_t blackCount = 0;
Node* cur = root;
while (cur)
{
if (cur->color == BLACK)
++blackCount;
cur = cur->left;
}
// 檢測每條路徑黑節點個數是否相同 第二個引數記錄路徑中黑節點的個數
return _IsValidRBTree(root, 0, blackCount);
}
bool _IsValidRBTree(Node* root, size_t k, size_t blackCount)
{
// 走到空就判斷該條路徑的黑節點是否等於blackCount
if (root == nullptr)
{
if (k != blackCount)
{
cout << "違反每條路徑黑節點個數相同的規則" << endl;
return false;
}
return true;
}
if (root->color == BLACK)
++k;
// 判斷是否出現了連續兩個紅色節點
Node* parent = root->parent;
if (parent && root->color == RED && parent->color == RED)
{
cout << "違反了不能出現連續兩個紅色節點的規則" << endl;
return false;
}
return _IsValidRBTree(root->left, k, blackCount)
&& _IsValidRBTree(root->right, k, blackCount);
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->left);
int rightHeight = _Height(root->right);
return 1 + max(leftHeight, rightHeight);
}
//中序遍歷(遞迴)
void InOrder()
{
_InOrder(root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == NULL)
{
return;
}
else
{
_InOrder(root->left);
cout << root->key << ":" << root->value << " ";
_InOrder(root->right);
}
}
public:
Node* root = nullptr;
};
void TestRBTree()
{
//srand((size_t)time(nullptr));
RB_Tree<int, int> rbt;
int b[] = { 0,1,2,3,4,5,6,7,8,9,10,11,12};
// int b[] = { 16,3,7,11,9,26,18,14,15 };
// int b[] = { 4,2,6,1,3,5,15,7,16,14 };
// int b[] = { 10,9,8,7,6,5,4,3,2,1 };
vector<int> a;
for (size_t i = 0; i < sizeof(b) / sizeof(int); ++i)
{
// a.push_back(rand());
a.push_back(b[i]);
}
//int a[] = { 4,2,6,7,3,5 };
/*vector<int> v;
v.reserve(100000);
for (size_t i = 1; i <= v.capacity(); ++i)
{
v.push_back(i);
}*/
for (auto e : a)
{
rbt.Insert(e,e);
cout << "插入資料 " << e << " 後:" << "樹的高度:" << rbt._Height(rbt.root) << " 是否為紅黑樹:" << rbt.IsValidRBTree();
cout << "列印二叉樹: ";
rbt.InOrder();
}
cout << "-------------------------------------------------------" << endl;
for (auto e : a)
{
rbt.Erase(e);
cout << "刪除資料 " << e << " 後:" << "樹的高度:" << rbt._Height(rbt.root) << " 是否為紅黑樹:" << rbt.IsValidRBTree();
cout << "列印二叉樹: ";
rbt.InOrder();
}
}
int main()
{
TestRBTree();
system("pause");
return EXIT_SUCCESS;
}
執行結果如下:
AVL樹和紅黑樹的比較
AVL樹 | 紅黑樹 | |
---|---|---|
如何控制平衡 | 通過條件平衡因子,子樹左右高度差不超過1 | 用過顏色控制,使得最長路徑不超出最短路徑的長度的兩倍 |
增刪查改的時間複雜度 | 可以穩定在O(logN) | 基本是O(logN),極端情況下是O(log2N) |
總結: AVL樹是嚴格意義上的平衡,紅黑樹是相對的平衡,兩者都很高效,但後者用的更多,因為它旋轉更是,實現相對簡單,付出的代價少一點。