1.爲何須要紅黑樹?html
對於二叉搜索樹,若是插入的數據是隨機的,那麼它就是接近平衡的二叉樹,平衡的二叉樹,它的操做效率(查詢,插入,刪除)效率較高,時間複雜度是O(logN)。可是可能會出現一種極端的狀況,那就是插入的數據是有序的(遞增或者遞減),那麼全部的節點都會在根節點的右側或左側,此時,二叉搜索樹就變爲了一個鏈表,它的操做效率就下降了,時間複雜度爲O(N),因此能夠認爲二叉搜索樹的時間複雜度介於O(logN)和O(N)之間,視狀況而定。那麼爲了應對這種極端狀況,紅黑樹就出現了,它是具有了某些特性的二叉搜索樹,能解決非平衡樹問題,紅黑樹是一種接近平衡的二叉樹。node
2.紅黑樹的特性有哪些?spa
首先,紅黑樹是一個二叉搜索樹,它同時知足如下特性:3d
(1) 每一個節點要麼是黑色,要麼是紅色code
(2) 根節點是黑色htm
(3) 若是節點是紅色的,那麼它的子節點必須是黑色的(反之,不必定須要成立)blog
(4) 從根節點到葉節點或空子節點的每條路徑,都包含相同數目的黑色節ip
經過看圖來理解以上四個特性get
3.紅黑樹的效率io
紅黑樹的查找,插入和刪除操做,時間複雜度都是O(logN)。查找操做時,它和普通的相對平衡的二叉搜索樹的效率相同,都是經過相同的方式來查找的,沒有用到紅黑樹特有的特性。但,若是插入的時候是有序數據,那麼紅黑樹的查詢效率就比二叉搜索樹要高了,由於此時二叉搜索樹不是平衡樹,它的時間複雜度O(N)。插入和刪除操做時,因爲紅黑樹的每次操做平均要旋轉一次和變換顏色,因此它比普通的二叉搜索樹效率要低一點,不過期間複雜度仍然是O(logN)。總之,紅黑樹的優勢就是對有序數據的查詢操做不會慢到O(logN)的時間複雜度。
4.對旋轉的理解
在紅黑樹中,插入或者刪除數據時,爲了保持紅黑樹的那五個特性,須要進行旋轉和變換顏色的操做。旋轉必需要一次性作兩件事情:
* 使一些節點上升,一些節點降低,幫助樹平衡
* 保證不破壞二叉搜索樹的特徵
旋轉分爲左旋轉和右旋轉,那麼咱們就看下左旋轉和右旋轉是怎麼回事。
4.1 左旋轉
此處,以50爲支點進行逆時針旋轉,而後75成爲了頂點,50成爲了75的左子節點,65成爲了50的右子節點,這個操做就是左旋轉。
4.2 右旋轉
此處,以75爲支點順時針旋轉,而後50成爲了頂點,75成爲了50的右子節點,65成爲了75的左子節點,這就是右旋轉操做。
5.插入操做
在介紹插入操做前,先約定一下各個節點的名稱。看圖:
在紅黑樹中,插入一個節點,都執行了哪些操做呢?
首先,查找要插入節點的位置,而後給予顏色(紅色或者黑色),可是爲了保證紅黑樹的特性,須要進行旋轉或者更改顏色,須要注意的是,在旋轉前和旋轉後,紅黑樹一直都是一個二叉搜索樹,二叉搜索樹的特徵從未改變過。
這裏,對於新插入的節點,咱們給予的顏色是紅色,是由於爲了和紅黑樹的第(4)條特性不衝突: 從根節點到葉節點或空子節點的每條路徑,都包含相同數目的黑色節點。這樣就少了不少操做。而後看其它幾個特性
* 對於(1)特性:每一個節點要麼是黑色,要麼是紅色,不衝突。
* 對於(2)特性:根節點是黑色,若是插入的節點不是根節點,也不衝突。若是是根節點,那麼直接給予顏色黑色
那麼,惟一須要知足的特性就是(3):若是節點是紅色的,那麼它的子節點必須是黑色的。這裏,當咱們給予新插入的節點顏色是紅色時,須要根據父節點的不一樣狀況作不一樣的處理,以知足這個特性。有如下幾種狀況:
根據代碼來理解這幾種狀況:
/* 紅黑樹修正程序 */ void insert_repair_tree(struct node* n) {
//狀況一:節點N的父節點爲null,說明該節點是根節點 if (parent(n) == NULL) { insert_case1(n); } else if (parent(n)->color == BLACK) {
//狀況二:父節點是黑色 insert_case2(n); } else if (uncle(n)->color == RED) {
//狀況三: 父節點和叔叔節點都是紅色 insert_case3(n); } else {
//狀況四:父節點是紅色,叔叔節點是黑色 insert_case4(n); } }
5.1:若是新插入節點是根節點,那麼給予該節點顏色是黑色
看代碼:
/* 狀況一:插入的節點是根節點 */ void insert_case1(struct node* n) { if (parent(n) == NULL) n->color = BLACK; }
5.2:若是新插入節點的父節點是黑色,那麼給予該節點是紅色不會對該紅黑樹有任何影響,因此不作任何處理。
看代碼:
/* 狀況二:父節點是黑色 */ void insert_case2(struct node* n) { return; /* Do nothing since tree is still valid */ }
5.3:若是新插入節點的父節點是紅色,同時叔叔節點也是紅色
須要將父親節點和叔叔節點從新繪製爲黑色,祖父節點繪製爲紅色。可是,若是此處祖父節點爲根節點,那麼須要調用紅黑樹修正程序,將祖父節點繪製爲黑色。
看代碼:發現看代碼比看圖要更容易理解,而且邏輯也更嚴謹,對於父親節點和叔叔節點都是紅色時,發現其餘文章都沒有考慮祖父節點爲根節點的狀況,這裏圖就省了。
/* 狀況三:父節點和叔叔節點都是紅色 */ void insert_case3(struct node* n) { parent(n)->color = BLACK; uncle(n)->color = BLACK; grandparent(n)->color = RED;
//調用紅黑樹修正程序 insert_repair_tree(grandparent(n)); }
5.4:若是新插入節點的父節點是紅色,同時叔叔節點是黑色
看代碼:
/* 狀況四:第一步 */ void insert_case4(struct node* n) { struct node* p = parent(n); struct node* g = grandparent(n); if (n == g->left->right) { /* 若是父節點是祖父節點的左子節點,新插入節點是父節點的右子節點,那麼以父節點爲支點左旋 */ rotate_left(p); n = n->left; } else if (n == g->right->left) { /* 若是父節點是祖父節點的右子節點,新插入節點是父節點的左子節點,那麼以父節點爲支點右旋 */ rotate_right(p); n = n->right; } insert_case4step2(n); }
/* 狀況四:第二步 */
/* 在第二步時:當前節點n確定處於 祖父節點左子節點的左側,或者祖父節點右子節點的右側 */ void insert_case4step2(struct node* n) {
struct node* p = parent(n); struct node* g = grandparent(n); if (n == p->left)
/* 以祖父節點爲支點進行右旋 */ rotate_right(g); else rotate_left(g); p->color = BLACK; g->color = RED; }
在知足父節點是紅色,叔叔節點是黑色的條件下,咱們能夠分如下幾種狀況:
狀況(a):父節點p是祖父節點g的左子節點,新插入節點n是父節點p的右子節點
處理:以父節點爲支點左旋,而後以祖父節點爲支點進行右旋,最後給父節點(第一次旋轉後的父節點)繪製爲黑色,給祖父節(第一次旋轉後的祖父節點)點繪製爲紅色
看圖:
狀況(b):父節點p是祖父節點g的右子節點,新插入節點n是父節點p的左子節點
處理:以父節點爲支點進行右旋,而後以祖父爲支點進行左旋,最後給父節點(第一次旋轉後的父節點)繪製爲黑色,給祖父節(第一次旋轉後的祖父節點)點繪製爲紅色
看圖:
狀況(c):父節點p是祖父節點g的左子節點,新插入節點n是父節點p的左子節點
處理:以祖父節點爲支點進行右旋,最後給父節點繪製爲黑色,給祖父節點繪製爲紅色,也就是直接調用第二步的方法 insert_case4step2
狀況(d):父節點p是祖父節點g的右子節點,新插入節點n是父節點p的右子節點
處理:以祖父爲支點進行左旋,最後給父節點繪製爲黑色,給祖父節點繪製爲紅色,也就是直接調用第二步的方法 insert_case4step2
6 刪除操做
對於刪除操做,處理的邏輯是:根據二叉搜索樹的特色找到被刪除的節點,將其刪除掉,而後再經過改變顏色和旋轉操做保持其紅黑樹的特性。此處咱們統一規定被刪除節點是D,真正被刪除節點爲RD,真正被刪除節點的兄弟節點是B,真正被刪除節點的父節點是P,B的兩個子節點是BL和BR。刪除操做有三種狀況:
6.1 被刪除節點爲葉子節點,分爲兩種狀況:
狀況(a):該葉子節點是紅色
處理:直接刪除
狀況(b):該葉子節點是黑色,
處理:刪除節點,而後旋轉和變換顏色。
6.2 被刪除節點只有一個子節點,那麼使被刪除節點的父節點指向被刪除節點的子節點,而後經過改變節點顏色和旋轉來保持紅黑樹特性
6.3 被刪除節點D有兩個子節點,那麼能夠將被刪除節點D的後繼節點(D的右子樹中的最小元素RD)的值賦值給被刪除節點D,可是被刪除節點D的顏色不作改變,此時刪除操做就轉化爲了刪除後繼節點RD的狀況了。那麼刪除RD的狀況又分爲五種:
狀況(a): RD節點是紅色的
處理:若是RD節點是紅色的,那麼它的父節點和右子節點應該是黑色的,當刪除RD節點時,直接把RD節點的父節點指向RD節點的右子節點便可。
下面四種狀況RD節點都是黑色的
狀況(b):RD的兄弟節點B爲紅色
處理:若是B節點爲紅色,那麼P節點和B的兩個子節點都是黑色。交換B節點和P節點的顏色,而後以P節點爲支點進行左旋轉。這樣處理後,就將狀況(b)轉爲(c),(d),(e)中的一種。
看圖:
狀況(c):
後續更新。。。
參考資料:
模擬紅黑樹:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
維基百科紅黑樹:https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Insertion