介紹紅黑樹以前,咱們先來簡單瞭解下二叉查找樹(BST)。java
首先咱們看下二叉查找樹的特性:spa
1.若任意節點的左子樹不空,則左子樹上全部結點的值均小於或等於它的根結點的值。3d
2.若任意節點的右子樹不空,則右子樹上全部結點的值均大於或等於它的根結點的值。code
3.左、右子樹也分別爲二叉排序樹。cdn
如上圖,這是一顆二叉樹,若是咱們要查詢7,那麼只要先跟8比,而後再跟3比,再跟6比,而後找到7,咱們能夠發現,這正是二分查找的思想,查詢二叉樹中的一個數據,最大的查詢次數就是這顆樹的高度。同理,當插入數據的時候,也是從根節點開始依次比較找到位置,再插入。可是二叉樹也存在着缺陷,那就是當出現跛腳樹的時候,二叉樹就會變成了線性查找。舉個例子。咱們有一個根節點8,若是依次插入7,6,5,4,那麼這棵二叉樹就會變成下面這個模樣:blog
我去,這仍是我認識的樹嘛。。。咱們發現,樹的高度就是元素的個數,查詢效率變成了O(n)。爲了解決二叉樹的平衡問題,下面咱們就來引出主角--紅黑樹(Red–black tree)。排序
紅黑樹(英語:Red–black tree)是一種自平衡二叉查找樹,是每一個節點都帶有顏色屬性的二叉查找樹,顏色爲紅色或黑色。在二叉查找樹強制通常要求之外,對於任何有效的紅黑樹咱們增長了以下的額外要求:
遞歸
下圖是一顆紅黑樹it
這5個特性,確保了二叉樹的大體平衡:從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。(最長路徑爲黑和紅相間隔,最短路徑爲全黑節點)io
當有插入和刪除操做的時候,紅黑樹的平衡最容易被打破,接下來咱們詳細分析下各類場景下的破壞,紅黑樹是如何保持平衡的。
這裏先明確一個點,咱們把新插入的節點標記爲紅色(爲何呢?若是設爲黑色,就會致使根到葉子的路徑上有一條路上,多一個額外的黑節點,這個是很難調整的。可是設爲紅色節點後,可能會致使出現兩個連續紅色節點的衝突,那麼能夠經過1⃣️變色和2⃣️樹旋轉來調整。)
爲了便於理解,如今開頭和你們普及幾個概念。(祖父節點,叔節點)
class Node {
Object value;
int color;
Node left, right, parent;
}
//祖父節點
Node grandparent(Node n) {
return n.parent.parent;
}
//叔叔節點
Node uncle(Node n) {
if(n.parent == grandparent(n).left)
return grandparent(n).right;
else
grandparent(n).left;
}複製代碼
插入場景1(變色):這個場景比較簡單,首次插入,根節點
void insert_case1(Node n){
if(n.parent == NULL) //根節點直接把顏色變黑就好了(變色)
n.color = BLACK;
return;
else //不然,進入場景2
insert_case2 (n);
}複製代碼
插入場景2:父節點爲黑色,直接插入紅色節點就好了(新插入的節點是在原先的葉子節點,不會有兩個紅色相連的問題)
void insert_case2(Node n){
if(n.parent.color == BLACK)
return; //父節點爲黑色,直接插入紅色節點就好了(不用操做)
else //不然
insert_case3 (n);
}複製代碼
插入場景3(變色):此時父節點存在且爲紅色,那麼祖父的節點確定是黑色。那麼要看uncle節點的狀況。假設uncle節點的顏色是紅色的
void insert_case3(Node n){
//若是uncle節點的顏色是紅色的
if(uncle(n) != NULL && uncle(n).color == RED) {
n.parent.color = BLACK; //把父節點變黑
uncle(n).color = BLACK; //uncle節點變黑
grandparent(n).color = RED; //祖父節點變紅
//此時祖父節點如下的都已經平衡了,剩下的就是要把祖父節點看成新插入的節點,去遞歸調用一下場景1開始的流程
insert_case1(grandparent(n));
} else
//若是uncle節點是黑色的,那麼看下場景4
insert_case4 (n);
}複製代碼
場景3操做圖以下:
插入場景4(左旋或者右旋):此時父節點爲紅色,祖父節點是黑色,uncle節點只能爲NIL節點(由於uncle爲黑的話,就違反了特性5)
void insert_case4(Node n){
//若是n爲右節點,n的父節點p爲祖父節點g的左節點,以下圖
if(n == n.parent.right && n.parent == grandparent(n).left) {
rotate_left(n.parent); //n的父節點執行--左旋操做
//旋轉完成後,n指向本身的左節點(這樣就變成了場景5的一種情形:本身是左節點,且父節點是祖父節點的左節點)
n = n.left;
} else if(n == n.parent.left && n.parent == grandparent(n).right) {
//若是本身是左節點,且父節點爲右節點
rotate_right(n.parent); //n的父節點執行--右旋操做
//旋轉完成後,n指向本身的右節點(這樣就變成了場景5的另外一種情形:本身是右節點,且父節點是祖父節點的右節點)
n = n.right;
}
//場景4的操做結果,或者直接知足場景5的,都將進入場景5作最後的操做
insert_case5 (n);
}複製代碼
場景4操做圖以下:
插入場景5:執行變色,以及祖父節點的旋轉
void insert_case5(Node n){
//首先執行變色操做,把n的父節點變黑,n的祖父節點變紅
n.parent.color = BLACK;
grandparent(n).color = RED;
//若是n爲左節點,n的父節點p爲n祖父節點G的左節點(如圖5-1)
if(n == n.parent.left && n.parent == grandparent(n).left) {
//n的祖父節點G右旋轉
rotate_right(grandparent(n));
} else {
//n爲右節點,n的父節點p爲n的祖父節點G的右節點(如圖5-2)
//n的祖父節點G左旋轉
rotate_left(grandparent(n));
}
}複製代碼
如5-1和5-2步驟圖所示,執行完畢後,從這棵子樹的根節點P到葉子節點的全部黑色節點數是2,與N插入前同樣(知足特性5),又由於P爲黑色,不會違反紅黑樹的其餘特性(特性1~4),因此,咱們插入的步驟就所有完成了。
若是須要刪除的節點有兩個兒子,那麼問題能夠被轉化成刪除另外一個只有一個兒子的節點的問題,對於二叉查找樹,在刪除帶有兩個非葉子兒子的節點的時候,咱們要麼找到它左子樹中的最大元素、要麼找到它右子樹中的最小元素,並把它的值轉移到要刪除的節點中。剩下的就是變色和旋轉的操做了,篇幅有限,這裏就不往下說了。