1. 紅/黑樹狀結構簡介
2. 紅/黑樹狀結構性質介紹
3. 漫遊紅/黑樹狀結構
4. 我的EasyCoding庫
5. 參考資料及代碼下載
<1>. 紅/黑樹狀結構簡介
紅/黑樹狀結構是一種平衡的二叉尋找樹,是一種電腦科學中常用的資料結構,最典型的應用是實現資料的關聯,例如map等資料結構的實現。1972年,魯道夫貝爾最先發明,但是他稱之為“對稱二叉B樹”,真正的稱之為“紅/黑樹狀結構”是在1978年Leo J. Guibas 和 Robert Sedgewick的一篇論文開始的。這麼算起來,紅/黑樹狀結構已經存在了將近30年,時至今日,仍舊另初學者頭痛不已。
<2>. 性質簡介
紅/黑樹狀結構拓展了二叉尋找樹,給每個樹的節點增加了一個Color屬性,每個節點必須是紅色或者是黑色,另外為了達到樹的平衡,增加了如下的限制(下面是用“限制”來指明):
1. 節點必須是紅色或者是黑色
2. 根節點是黑色的,如果不是黑色,根據下面的性質4,將無法插入新節點
3. 所有的葉子節點是黑色的,這裡可以增加哨兵元素,作為葉子節點,該節點是黑色,也就是說該條件是一定滿足的。
4. 每個紅色節點的兩個子節點是黑色的,也就是不能存在父子兩個節點全是紅色
5. 從任意每個節點到其每個葉子節點的所有簡單路徑上黑色節點的數量是相同的。
上面的這些約束保證了這個樹大致上是平衡的,這也決定了紅/黑樹狀結構的插入,刪除,查詢等操作是比較快速的。
<3>. 漫遊紅/黑樹狀結構
很多初學者在學習紅/黑樹狀結構時,總是首先查看別人的思路(比如《演算法導論》等),然後基本上就陷入了泥潭,那麼我們不妨靜下心來自己想一想如果我去實現需要該怎麼做?好的,準備好,開始漫遊紅/黑樹狀結構的?這裡不僅僅是列出插入操作分為那些情況,更重要的是知道問什麼這麼分類?
好的,現在我們是一無所有,假設這是第一次插入一個節點,為了滿足上面的1-5條性質,很顯然僅僅需要很少量的操作即可:
產生一個新節點,並拷貝資料;
節點顏色表明為黑色;
通過上面的操作產生的只有一個節點的樹,滿足1-5條性質,顯然該樹是紅/黑樹狀結構。
特殊情況考慮完成之後,下面假設又開始添加節點,我們面對的第一個問題是新增加的節點是標記成紅色還是黑色?顯然無論是新插入的節點是黑色或者是紅色,紅/黑樹狀結構限制1,2,3一定是滿足的,那麼如果將新插入的節點標識成黑色的話,可能違反5,但是如果將新插入的節點標識成紅色,肯能違反4,看似好像是兩個是類似的。
但是考慮這樣的一種情況:如果新插入的節點標識成紅色,並且新插入的節點的父節點是黑色,那麼是違反性質4的,也就是說是不需要重新調整紅/黑樹狀結構的。
但是如果標識成黑色的話,那麼是一定會違反性質5,好的,我們還是選擇將插入的節點標識成紅色吧,至少運氣好的話,就不需要重新調整紅/黑樹狀結構了。
確定了新插入的節點的顏色之後,現在開始具體的實現插入操作,由於紅/黑樹狀結構實際上也是一種二叉尋找樹,那麼新插入的頂點一定是在紅/黑樹狀結構的最低端,我們忽略掉這個尋找節點的過程,這裡僅僅關心插入節點之後如何調整紅/黑樹狀結構。數學上的常用方法:分類討論
case 1. 如果插入的節點是根節點,也就是說初始的紅/黑樹狀結構為空白,這是最簡單的情況,直接將該節點標識成黑色即可。
void insert_case1(struct node *n)
{
if (n->parent == NULL)
n->color = BLACK;
else
insert_case2(n);
}
case 2. 如果新插入的節點不是紅/黑樹狀結構的根節點,如果新插入的節點的父節點是黑色的話,那麼紅/黑樹狀結構是不需要調整的
代碼:
void insert_case2(struct node *n)
{
if (n->parent->color == BLACK)
return; /* Tree is still valid */
else
insert_case3(n);}
case 3. 如果新插入的節點的父節點是紅色的話,顯然這裡違反了紅/黑樹狀結構中不能存在父節點和子節點同時為紅色的性質,。
於是需要調整,我們的調整的目標是在不違反紅/黑樹狀結構性質(或者是可調整)如何將兩個相鄰的紅色節點分隔開來,顯然最終的結果是我們需要將新插入的節點的父節點更改成黑色(新插入節點是如法作出調整的,實現將兩個紅色節點分割開的,除非將該節點標識為黑色,但是這會增加黑高度),但是如果是單純的修改父節點為黑色的話,那麼將會違背黑色頂點數目的性質,直接修改是行不通,那麼能否通過交換兩個兩個節點達到目的呢?
顯然新插入的節點的祖父節點一定是黑色的,那麼是否能夠通過交換父節點和祖父節點的紅黑性質來達到目的呢?顯然這可能違反將新插入節點的叔叔子樹的紅/黑樹狀結構性質破壞掉。但是如果叔叔節點是紅色的話,問題似乎變得很簡單了。
我們僅僅需要將主父節點改成紅色,同時將父節點和叔叔節點改成黑色即可。
但是這將引入新的問題, 顯然我們將G節點表示成紅色之後,那麼G節點和G的父節點是可能違反紅黑性質的,為瞭解決這個問題,這裡使用尾遞迴的形式來對節點G進行調整。
void insert_case3(struct node *n)
{
struct node *u = uncle(n), *g;
if ((u != NULL) && (u->color == RED)) {
n->parent->color = BLACK;
u->color = BLACK;
g = grandparent(n);
g->color = RED;
insert_case1(g);
} else {
insert_case4(n);
}
}
一種情況已經解決。下面將是更加艱巨的任務,如果叔叔節點是黑色的,該如何是好?
case 4:我們來看看還剩下的就是這種情況了,P為紅色,U為黑色,G為黑色,怎麼解決?對了,到這裡是否忘了我們的最終目的是什嗎?
在不違反紅/黑樹狀結構性質(或者是可調整)如何將兩個相鄰的紅色節點分隔開來.
直接將P節點修改成黑色是不行的,這將增加G-P-U路徑上的黑高度, 沒有思路,好的,來回頭看看case 3的情況,畢竟已經解決了一種情況,case 3中我們修改了節點G的顏色,為什麼能夠修改,顯然是說G是最頂點(輩份最高)的節點,最頂點的節點的顏色可以是紅色(重新調整)或者是黑色。
那如果我們呢將P節點提升至現在G的位置呢(通過樹的旋轉)?
現在可以將P的顏色標識成黑色,但是會破壞P-G-U的黑路徑,運用一下交換的思想吧,交換P和G的顏色即可。
現在基本上已經解決了紅/黑樹狀結構中如果叔叔是黑色的情況,剩下的工作就是分析旋轉方向,根據節點P和N是左孩子還是右孩子進行相應旋轉。
這是case 4情況:
或者是:
void insert_case4(struct node *n)
{
struct node *g = grandparent(n);
if ((n == n->parent->right) && (n->parent == g->left)) {
rotate_left(n->parent);
n = n->left;
} else if ((n == n->parent->left) && (n->parent == g->right)) {
rotate_right(n->parent);
n = n->right;
}
insert_case5(n);}
這樣將P和U不再同一條直線的情景轉換成在統一條直線上的情況,case 5我們處理在同一條直線上的情況:
case 5:
或者是:
void insert_case5(struct node *n)
{
struct node *g = grandparent(n);
n->parent->color = BLACK;
g->color = RED;
if ((n == n->parent->left) && (n->parent == g->left)) {
rotate_right(g);
} else { /* (n == n->parent->right) and (n->parent == g->right) */
rotate_left(g);
}
}
紅/黑樹狀結構的插入的所有情況分析完了,也就是說我們的分類討論是完整的嗎?
好的,看來我們窮舉了所有的紅/黑樹狀結構的插入情況,紅/黑樹狀結構插入漫遊完畢。 這裡有一個applet的動畫示範,很生動。
轉載自:http://www.cnblogs.com/xuqiang/archive/2011/05/16/2047001.html,感謝原作者