二叉樹:二叉樹是每一個結點最多有兩個子樹的樹結構;一般子樹被稱做「左子樹」(left subtree)和「右子樹」(right subtree);二叉樹常被用於實現二叉查找樹和二叉堆;html
平衡二叉樹:又被稱爲AVL樹(有別於AVL算法),且具備如下性質:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹。平衡二叉樹的經常使用實現方法有紅黑樹、AVL、替罪羊樹、Treap、伸展樹等;java
排序二叉樹:任何節點的鍵值必定大於其左子樹中的每個節點的鍵值,並小於其右子樹中的每個節點的鍵值;算法
紅黑樹: spa
紅黑樹在原有的排序二叉樹增長了以下幾個要求:code
性質 1:每一個節點要麼是紅色,要麼是黑色。
性質 2:根節點永遠是黑色的。
性質 3:全部的葉節點都是空節點(即 null,其實是不存在的節點),而且是黑色的。
性質 4:每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的路徑上不會有兩個連續的紅色節點)
性質 5:從任一節點到其子樹中每一個葉子節點的路徑都包含相同數量的黑色節點。htm
紅黑樹除了具備排序二叉樹特性也屬於平衡二叉樹,但不是嚴格的平衡二叉樹,說它不嚴格是由於它不是嚴格控制左、右子樹高度或節點數之差小於等於1,但紅黑樹高度依然是平均log(n),且最壞狀況高度不會超過2log(n),這有數學證實。因此它算平衡樹。blog
在樹的結構發生改變時(插入或者刪除操做),每每會破壞上述條件4或條件5,須要經過調整使得查找樹從新知足紅黑樹的條件;須要經過調整使得查找樹從新知足紅黑樹的條件。排序
調整能夠分爲兩類:一類是顏色調整,即改變某個節點的顏色;另外一類是結構調整,集改變檢索樹的結構關係。結構調整過程包含兩個基本操做:左旋(RotateLeft),右旋(RotateRight)。遞歸
左旋的過程是將x
的右子樹繞x
逆時針旋轉,使得x
的右子樹成爲x
的父親(x成爲其右子樹的左節點),同時修改相關節點的引用。旋轉以後,二叉查找樹的屬性仍然知足。get
以下,3--->9--->10這個鏈即是以3節點爲當前節點,3節點的右子樹9--->10逆時針左旋的效果,最後3成了9的左節點,三、九、10達成了平衡
右旋的過程是將x
的左子樹繞x
順時針旋轉,使得x
的左子樹成爲x
的父親(x成爲其左子樹的右節點),同時修改相關節點的引用。旋轉以後,二叉查找樹的屬性仍然知足。
以下,3--->2--->1這個鏈即是以3節點爲當前節點,3節點的左子樹2--->1順時針時針右旋的效果,最後3成了2的右節點,三、二、1達成了平衡;
增長節點後有哪些場景是須要調整的?
在一個已有的紅黑樹中增長一個新節點,假設3爲祖父節點,2或4爲父(叔)節點,新加入的節點爲當前節點,當前新增節點做爲2或4的子節點;能夠用排除法來一個個排除,場景以下:
綜合分析,一顆存在紅黑樹自己處於相對穩定狀態(沒有外力能觸發調整),穩定的紅黑樹中只會存在上述無影響和待調整的結構圖,不會出現不存在的結構圖;而無影響的結構圖新增節點並不破壞紅黑樹特性,因此待處理的就剩下待調整的三種了,接下來分析這三種。
注意,看圖時注意結構,不要盯着純數字,由於不一樣場景,數字表明的節點含義不同
場景一:祖父節點黑色、父節點爲祖父節點的左節點、無叔叔節點
這種狀況,若是是孫節點做爲父節點的右節點加入,對應1.1開始;若是孫節點做爲父節點的左節點加入,對應1.2開始;
場景二:祖父節點黑色、父節點爲祖父節點的右節點、無叔叔節點
這種狀況,若是是孫節點做爲父節點的左節點加入,對應2.1開始;若是孫節點做爲父節點的右節點加入,對應2.2開始;
場景三:祖父節點黑色、父節點紅色、叔叔節點紅色
這種狀況,若是不管孫節點是做爲父節點的左節點仍是右節點,或者不管父節點是哪個紅色節點,處理方式都是統一的
這種場景下,最後一步,祖父節點變成了紅色,而祖父節點的父節點可能以前也是紅色的,因此可能違背了紅黑樹性質,因此纔會有最後一步將祖父節點設爲「當前節點」,而後就成爲了場景一和二的狀況,這是一個遞歸的過程直到當前節點的父節點顏色是黑色。
參考java中TreeMap的實現,來看是否和上述分析一致
fixAfterInsertion是每次向TreeMap中新增節點後都會調用的修正方法,正式這個方法保證和紅黑樹的性質,與之對應的有fixAfterDeletion(刪除節點後調用)
看這個方法的邏輯,徹底與上述分析一致。
1 private void fixAfterInsertion(Entry<K,V> x) { 2 // 新增節點都是紅色 3 x.color = RED; 4 5 // 遞歸處理,當前節點的父節點是紅色就要一直處理 6 while (x != null && x != root && x.parent.color == RED) { 7 // 父節點爲祖父節點的左節點 8 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { 9 Entry<K,V> y = rightOf(parentOf(parentOf(x))); 10 if (colorOf(y) == RED) { 11 // 叔叔節點爲紅色,父、叔節點置黑,祖父節點置紅,以祖父節點爲當前節點遞歸 12 setColor(parentOf(x), BLACK); 13 setColor(y, BLACK); 14 setColor(parentOf(parentOf(x)), RED); 15 x = parentOf(parentOf(x)); 16 } else { 17 if (x == rightOf(parentOf(x))) { 18 // 父節點左旋爲祖父節點的右旋騰出位置(孫父祖三代處於同一斜率,依次爲左子節點) 19 x = parentOf(x); 20 rotateLeft(x); 21 } 22 // 父節點置黑、祖父節點置紅、祖父節點右旋 23 setColor(parentOf(x), BLACK); 24 setColor(parentOf(parentOf(x)), RED); 25 rotateRight(parentOf(parentOf(x))); 26 } 27 } 28 // 父節點爲祖父節點的右節點 29 else { 30 Entry<K,V> y = leftOf(parentOf(parentOf(x))); 31 if (colorOf(y) == RED) { 32 // 叔叔節點爲紅色,父、叔節點置黑,祖父節點置紅,以祖父節點爲當前節點遞歸 33 setColor(parentOf(x), BLACK); 34 setColor(y, BLACK); 35 setColor(parentOf(parentOf(x)), RED); 36 x = parentOf(parentOf(x)); 37 } else { 38 if (x == leftOf(parentOf(x))) { 39 // 父節點右旋爲祖父節點的左旋騰出位置(孫父祖三代處於同一斜率,依次爲右子節點) 40 x = parentOf(x); 41 rotateRight(x); 42 } 43 // 父節點置黑、祖父節點置紅、祖父節點左旋 44 setColor(parentOf(x), BLACK); 45 setColor(parentOf(parentOf(x)), RED); 46 rotateLeft(parentOf(parentOf(x))); 47 } 48 } 49 } 50 51 // 根節點置黑 52 root.color = BLACK; 53 }
爲甚麼紅黑樹中新增長的節點必定是紅色?
紅黑樹的5個性質中,性質4和性質5是比較容易違背的,爲了儘可能避免由於破壞紅黑樹的特性而作調整,每次新插入的節點都是紅色。由於插入以前全部根至外部節點的路徑上黑色節點數目都相同,若是插入的節點是黑色確定錯誤(黑色節點數目不相同),而相對的插入紅節點可能會也可能不會違反「沒有連續兩個節點是紅色」這一條件,因此插入的節點爲紅色代價相對小,若是違反條件再調整。
爲何基於「子節點-->父節點-->祖父節點」來調整紅黑樹的平衡?
在進行顏色變化或旋轉的時候,每每要涉及祖孫三代節點(X表示操做的基準節點,P表明X的父節點,G表明X的父節點的父節點);這是由於基於至少三個節點來旋轉調色能夠儘可能保持局部知足紅黑樹的5個特性,這樣就能儘可能不破壞總體特性;若是隻有兩個節點子和父,就算知道破壞了紅黑樹的性質也無法經過自我調整來達到效果,只有兩個節點旋轉來旋轉去也不平衡。
爲何場景一和場景二中,孫節點做爲父節點的左節點和右節點處理場景不同?
拿場景一來講,若是不將下圖中的1和2節點進行一次左旋,那麼3節點在右旋的時候2會成爲3的左節點,不能同時保證性質4和5;假設紅黑樹只有三個節點,右旋以後1成爲根節點,3是1的右節點,2是3的左節點;因爲1是黑色且3只能爲紅色,那麼2節點爲黑色破壞了性質5,2節點爲紅色破壞了性質4;
有人問爲何不把3往左旋,其實仔細想一想紅黑樹的默認排序規則,一個節點的值必定大於其左子樹小於其右子樹。所以,保證孫--->父--->祖三代節點都在同一斜率(依次爲左子節點或依次爲右子節點)能夠同時知足紅黑樹的五種性質而且儘量保證紅黑樹的平衡。
待補充
PS:在線生成紅黑樹鏈接:https://sandbox.runjs.cn/show/2nngvn8w
參考: