紅黑樹與平衡二叉樹_百圖詳解紅黑樹,想不理解都難

之前在公司組內分享了紅黑樹的工作原理,今天把它整理下發出來,希望能對大家有所幫助,對自己也算是一個知識點的總結。

這篇文章算是我寫博客寫公眾號以來畫圖最多的一篇文章了,沒有之一,我希望盡可能多地用圖片來形象地描述紅黑樹的各種操作的前后變換原理,幫助大家來理解紅黑樹的工作原理,下面,多圖預警開始了。

在講紅黑樹之前,我們首先來了解下下面幾個概念:二叉樹,排序二叉樹以及平衡二叉樹。

二叉樹

二叉樹指的是每個節點最多只能有兩個字數的有序樹。通常左邊的子樹稱為左子樹 ,右邊的子樹稱為右子樹 。這里說的有序樹強調的是二叉樹的左子樹和右子樹的次序不能隨意顛倒。

二叉樹簡單的示意圖如下:

59f2500a1634cef006e8d458d871e7c0.png

代碼定義:

class Node {T data;Node left;Node right;
}

排序二叉樹

所謂排序二叉樹,顧名思義,排序二叉樹是有順序的,它是一種特殊結構的二叉樹,我們可以對樹中所有節點進行排序和檢索。

性質
  • 若它的左子樹不空,則左子樹上所有節點的值均小于它的根節點的值;
  • 若她的右子樹不空,則右子樹上所有節點的值均大于它的根節點的值;
  • 具有遞歸性,排序二叉樹的左子樹、右子樹也是排序二叉樹。

排序二叉樹簡單示意圖:

a8217217115639ca3ec986a681a5c006.png

排序二叉樹退化成鏈表

排序二叉樹的左子樹上所有節點的值小于根節點的值,右子樹上所有節點的值大于根節點的值,當我們插入一組元素正好是有序的時候,這時會讓排序二叉樹退化成鏈表。

正常情況下,排序二叉樹是如下圖這樣的:

965fbd0fb859750ab4ab0f37ef1a1a52.png

但是,當插入的一組元素正好是有序的時候,排序二叉樹就變成了下邊這樣了,就變成了普通的鏈表結構,如下圖所示:

047067a132e7ce3ad93f620c993d3ec9.png

正常情況下的排序二叉樹檢索效率類似于二分查找,二分查找的時間復雜度為 O(log n),但是如果排序二叉樹退化成鏈表結構,那么檢索效率就變成了線性的 O(n) 的,這樣相對于 O(log n) 來說,檢索效率肯定是要差不少的。

思考,二分查找和正常的排序二叉樹的時間復雜度都是 O(log n),那么為什么是O(log n) ?

關于 O(log n) 的分析下面這篇文章講解的非常好,感興趣的可以看下這篇文章 二分查找的時間復雜度.md),文章是拿二分查找來舉例的,二分查找和平衡二叉樹的時間復雜度是一樣的,理解了二分查找的時間復雜度,再來理解平衡二叉樹就不難了,這里就不贅述了。

繼續回到我們的主題上,為了解決排序二叉樹在特殊情況下會退化成鏈表的問題(鏈表的檢索效率是 O(n) 相對正常二叉樹來說要差不少),所以有人發明了平衡二叉樹紅黑樹類似的平衡樹。

平衡二叉樹

平衡二叉數又被稱為 AVL 樹,AVL 樹的名字來源于它的發明作者 G.M. Adelson-Velsky 和 E.M. Landis,取自兩人名字的首字母。

官方定義:它或者是一顆空樹,或者具有以下性質的排序二叉樹:它的左子樹和右子樹的深度之差(平衡因子)的絕對值不超過1,且它的左子樹和右子樹都是一顆平衡二叉樹。

兩個條件:

  • 平衡二叉樹必須是排序二叉樹,也就是說平衡二叉樹他的左子樹所有節點的值必須小于根節點的值,它的右子樹上所有節點的值必須大于它的根節點的值。
  • 左子樹和右子樹的深度之差的絕對值不超過1。

紅黑樹

講了這么多概念,接下來主角紅黑樹終于要上場了。

為什么有紅黑樹?

其實紅黑樹和上面的平衡二叉樹類似,本質上都是為了解決排序二叉樹在極端情況下退化成鏈表導致檢索效率大大降低的問題,紅黑樹最早是由 Rudolf Bayer 于 1972 年發明的。

紅黑樹首先肯定是一個排序二叉樹,它在每個節點上增加了一個存儲位來表示節點的顏色,可以是 RED 或 BLACK 。

Java 中實現紅黑樹大概結構圖如下所示:

e16986036522ef6dcfe8273408336b94.png

紅黑樹的特性

  • 性質1:每個節點要么是紅色,要么是黑色。
  • 性質2:根節點永遠是黑色的。
  • 性質3:所有的葉子節點都是空節點(即null),并且是黑色的。
  • 性質4:每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的路徑上不會有兩個連續的紅色節點。)
  • 性質5:從任一節點到其子樹中每個葉子節點的路徑都包含相同數量的黑色節點。

針對上面的 5 種性質,我們簡單理解下,對于性質 1 和性質 2 ,相當于是對紅黑樹每個節點的約束,根節點是黑色,其他的節點要么是紅色,要么是黑色。

對于性質 3 中指定紅黑樹的每個葉子節點都是空節點,而且葉子節點都是黑色,但 Java 實現的紅黑樹會使用 null 來代表空節點,因此我們在遍歷 Java里的紅黑樹的時候會看不到葉子節點,而看到的是每個葉子節點都是紅色的,這一點需要注意。

對于性質 5,這里我們需要注意的是,這里的描述是從任一節點,從任一節點到它的子樹的每個葉子節點黑色節點的數量都是相同的,這個數量被稱為這個節點的黑高。

如果我們從根節點出發到每個葉子節點的路徑都包含相同數量的黑色節點,這個黑色節點的數量被稱為樹的黑色高度。樹的黑色高度和節點的黑色高度是不一樣的,這里要注意區分。

其實到這里有人可能會問了,紅黑樹的性質說了一大堆,那是不是說只要保證紅黑樹的節點是紅黑交替就能保證樹是平衡的呢?

其實不是這樣的,我們可以看來看下面這張圖:

00dbf48fba4f52e8568e1ef1ab136f90.png

左邊的子樹都是黑色節點,但是這個紅黑樹依然是平衡的,5 條性質它都滿足。

這個樹的黑色高度為 3,從根節點到葉子節點的最短路徑長度是 2,該路徑上全是黑色節點,包括葉子節點,從根節點到葉子節點最長路徑為 4,每個黑色節點之間會插入紅色節點。

通過上面的性質 4 和性質 5,其實上保證了沒有任何一條路徑會比其他路徑長出兩倍,所以這樣的紅黑樹是平衡的。

其實這算是一個推論,紅黑樹在最差情況下,最長的路徑都不會比最短的路徑長出兩倍。其實紅黑樹并不是真正的平衡二叉樹,它只能保證大致是平衡的,因為紅黑樹的高度不會無限增高,在實際應用用,紅黑樹的統計性能要高于平衡二叉樹,但極端性能略差。

紅黑樹的插入

想要徹底理解紅黑樹,除了上面說到的理解紅黑樹的性質以外,就是理解紅黑樹的插入操作了。

紅黑樹的插入和普通排序二叉樹的插入基本一致,排序二叉樹的要求是左子樹上的所有節點都要比根節點小,右子樹上的所有節點都要比跟節點大,當插入一個新的節點的時候,首先要找到當前要插入的節點適合放在排序二叉樹哪個位置,然后插入當前節點即可。紅黑樹和排序二叉樹不同的是,紅黑樹需要在插入節點調整樹的結構來讓樹保持平衡。

一般情況下,紅黑樹中新插入的節點都是紅色的,那么,為什么說新加入到紅黑樹中的節點要是紅色的呢?

這個問題可以這樣理解,我們從性質5中知道,當前紅黑樹中從根節點到每個葉子節點的黑色節點數量是一樣的,此時假如新的黑色節點的話,必然破壞規則,但加入紅色節點卻不一定,除非其父節點就是紅色節點,因此加入紅色節點,破壞規則的可能性小一些。

接下來我們重點來講紅黑樹插入新節點后是如何保持平衡的。

給定下面這樣一顆紅黑樹:

a5ac65be31a489d24a77b5008c9338ae.png

當我們插入值為66的節點的時候,示意圖如下:

c7e1ce62b2c69cef42e2e58512e79871.png

很明顯,這個時候結構依然遵循著上述5大特性,無需啟動自動平衡機制調整節點平衡狀態。

如果再向里面插入值為51的節點呢,這個時候紅黑樹變成了這樣。

f05ead8362ff3f4b44c5f7fb67acd012.png

這樣的結構實際上是不滿足性質4的,紅色兩個子節點必須是黑色的,而這里49這個紅色節點現在有個51的紅色節點與其相連。

這個時候我們需要調整這個樹的結構來保證紅黑樹的平衡。

首先嘗試將49這個節點設置為黑色,如下示意圖。

dd7fcd68c5545ac7a2210c6656ecd152.png

這個時候我們發現黑高是不對的,其中 60-56-45-49-51-null 這條路徑有 4 個黑節點,其他路徑的黑色節點是 3 個。

接著調整紅黑樹,我們再次嘗試把45這個節點設置為紅色的,如下圖所示:

0fb85596c2daaf87435b1ab84e725923.png

這個時候我們發現問題又來了,56-45-43 都是紅色節點的,出現了紅色節點相連的問題。

于是我們需要再把 56 和 43 設置為黑色的,如下圖所示。

c4e2f68a32eda7c7c3e2c8e8789510c6.png

于是我們把 68 這個紅色節點設置為黑色的。

f596ef2fae6b2dda95f9ff7a9b23a1d0.png

對于這種紅黑樹插入節點的情況下,我們可以只需要通過變色就可以保持樹的平衡了。但是并不是每次都是這么幸運的,當變色行不通的時候,我們需要考慮另一個手段就是旋轉了。

例如下面這種情況,同樣還是拿這顆紅黑樹舉例。

c7e1ce62b2c69cef42e2e58512e79871.png

現在這顆紅黑樹,我們現在插入節點65。

2a1543927cc3a846c242f70d3a201bdd.png

我們嘗試把 66 這個節點設置為黑色,如下圖所示。

371eb9662a87224360a9be08d35ef2f8.png

這樣操作之后黑高又出現不一致的情況了,60-68-64-null 有 3 個黑色節點,而60-68-64-66-null 這條路徑有 4 個黑色節點,這樣的結構是不平衡的。

或者我們把 68 設置為黑色,把 64 設置為紅色,如下圖所示:

7ed8e727e1a564b58a0f853e47860248.png

但是,同樣的問題,上面這顆紅黑樹的黑色高度還是不一致,60-68-64-null 和 60-68-64-66-null 這兩條路徑黑色高度還是不一致。

這種情況如果只通過變色的情況是不能保持紅黑樹的平衡的。

紅黑樹的旋轉

接下來我們講講紅黑樹的旋轉,旋轉分為左旋和右旋。

左旋

文字描述:逆時針旋轉兩個節點,讓一個節點被其右子節點取代,而該節點成為右子節點的左子節點。

文字描述太抽象,接下來看下圖片展示。

首先斷開節點PL與右子節點G的關系,同時將其右子節點的引用指向節點C2;然后斷開節點G與左子節點C2的關系,同時將G的左子節點的應用指向節點PL。

0fefb8f61154d9df251d04d3dc3ac90d.png

接下來再放下 gif 圖,希望能幫助大家更好地理解左旋,圖片來自網絡。

630908574bded1db1d78ff39ff162e2e.gif

右旋

文字描述:順時針旋轉兩個節點,讓一個節點被其左子節點取代,而該節點成為左子節點的右子節點。

右旋的圖片展示:

首先斷開節點G與左子節點PL的關系,同時將其左子節點的引用指向節點C2;然后斷開節點PL與右子節點C2的關系,同時將PL的右子節點的應用指向節點G。

08f5843582680c80ff2c45fce7279ee9.png

右旋的gif展示(圖片來自網絡):

d7292eed305934aa00de929a1a4ee886.gif

介紹完了左旋和右旋基本操作,我們來詳細介紹下紅黑樹的幾種旋轉場景。

左左節點旋轉(插入節點的父節點是左節點,插入節點也是左節點)

如下圖所示的紅黑樹,我們插入節點是65。

20ec470c0cc8e43340783047ff9ee0ab.png

操作步驟如下可以圍繞祖父節點 69 右旋,再結合變色,步驟如下所示:

30f013601f36e87b19f858a1c38a8151.png

左右節點旋轉(插入節點的父節點是左節點,插入節點是右節點)

還是上面這顆紅黑樹,我們再插入節點 67。

20ec470c0cc8e43340783047ff9ee0ab.png

這種情況我們可以這樣操作,先圍繞父節點 66 左旋,然后再圍繞祖父節點 69 右旋,最后再將 67 設置為黑色,把 69 設置為紅色,如下圖所示。

f2289e4b1cf4aa5a84fa04a28609adcf.png

右左節點旋轉(插入節點的父節點是右節點,插入節點左節點)

如下圖這種情況,我們要插入節點68。

a3d74156f6d7a5a24d47814a50266fe3.png

這種情況,我們可以先圍繞父節點 69 右旋,接著再圍繞祖父節點 66 左旋,最后把 68 節點設置為黑色,把 66 設置為紅色,我們的具體操作步驟如下所示。

dd4101ea326dec02aa7f62d0c351171b.png

右右節點旋轉(插入節點的父節點是右節點,插入節點也是右節點)

還是來上面的圖來舉例,我們在這顆紅黑樹上插入節點 70 。

a3d74156f6d7a5a24d47814a50266fe3.png

我們可以這樣操作圍繞祖父節點 66 左旋,再把旋轉后的根節點 69 設置為黑色,把 66 這個節點設置為紅色。具體可以參看下圖:

6f7b508b9af72094d7ceb89b16f5cbe2.png

紅黑樹在 Java 中的實現

Java 中的紅黑樹實現類是 TreeMap ,接下來我們嘗試從源碼角度來逐行解釋 TreeMap 這一套機制是如何運作的。

// TreeMap中使用Entry來描述每個節點static final class Entry<K,V> implements Map.Entry<K,V> {K key;V value;Entry<K,V> left;Entry<K,V> right;Entry<K,V> parent;boolean color = BLACK;...}
復制代碼

TreeMap 的put方法。

public V put(K key, V value) {//先以t保存鏈表的root節點Entry<K,V> t = root;//如果t=null,表明是一個空鏈表,即該TreeMap里沒有任何Entry作為rootif (t == null) {compare(key, key); // type (and possibly null) check//將新的key-value創建一個Entry,并將該Entry作為rootroot = new Entry<>(key, value, null);size = 1;//記錄修改次數加1modCount++;return null;}int cmp;Entry<K,V> parent;// split comparator and comparable pathsComparator<? super K> cpr = comparator;//如果比較器cpr不為null,即表明采用定制排序if (cpr != null) {do {//使用parent上次循環后的t所引用的Entryparent = t;//將新插入的key和t的key進行比較cmp = cpr.compare(key, t.key);//如果新插入的key小于t的key,t等于t的左邊節點if (cmp < 0)t = t.left;//如果新插入的key大于t的key,t等于t的右邊節點    else if (cmp > 0)t = t.right;else//如果兩個key相等,新value覆蓋原有的value,并返回原有的valuereturn t.setValue(value);} while (t != null);}else {if (key == null)throw new NullPointerException();@SuppressWarnings("unchecked")Comparable<? super K> k = (Comparable<? super K>) key;do {parent = t;cmp = k.compareTo(t.key);if (cmp < 0)t = t.left;else if (cmp > 0)t = t.right;elsereturn t.setValue(value);} while (t != null);}//將新插入的節點作為parent節點的子節點Entry<K,V> e = new Entry<>(key, value, parent);//如果新插入key小于parent的key,則e作為parent的左子節點if (cmp < 0)parent.left = e;//如果新插入key小于parent的key,則e作為parent的右子節點elseparent.right = e;//修復紅黑樹fixAfterInsertion(e);size++;modCount++;return null;}
復制代碼
//插入節點后修復紅黑樹
private void fixAfterInsertion(Entry<K,V> x) {x.color = RED;//直到x節點的父節點不是根,且x的父節點是紅色while (x != null && x != root && x.parent.color == RED) {//如果x的父節點是其父節點的左子節點if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//獲取x的父節點的兄弟節點Entry<K,V> y = rightOf(parentOf(parentOf(x)));//如果x的父節點的兄弟節點是紅色if (colorOf(y) == RED) {     //將x的父節點設置為黑色setColor(parentOf(x), BLACK);//將x的父節點的兄弟節點設置為黑色setColor(y, BLACK);//將x的父節點的父節點設為紅色setColor(parentOf(parentOf(x)), RED);x = parentOf(parentOf(x));}//如果x的父節點的兄弟節點是黑色else {   //TODO 對應情況第二種,左右節點旋轉//如果x是其父節點的右子節點if (x == rightOf(parentOf(x))) {//將x的父節點設為xx = parentOf(x);//右旋轉rotateLeft(x);}//把x的父節點設置為黑色setColor(parentOf(x), BLACK);//把x的父節點父節點設為紅色setColor(parentOf(parentOf(x)), RED);rotateRight(parentOf(parentOf(x)));}}//如果x的父節點是其父節點的右子節點else {//獲取x的父節點的兄弟節點Entry<K,V> y = leftOf(parentOf(parentOf(x)));//只著色的情況對應的是最開始例子,沒有旋轉操作,但是要對應多次變換//如果x的父節點的兄弟節點是紅色  if (colorOf(y) == RED) {//將x的父節點設置為黑色setColor(parentOf(x), BLACK);//將x的父節點的兄弟節點設為黑色setColor(y, BLACK);//將X的父節點的父節點(G)設置紅色setColor(parentOf(parentOf(x)), RED);//將x設為x的父節點的節點x = parentOf(parentOf(x));}//如果x的父節點的兄弟節點是黑色else {//如果x是其父節點的左子節點if (x == leftOf(parentOf(x))) {//將x的父節點設為xx = parentOf(x);//右旋轉rotateRight(x);}//將x的父節點設為黑色setColor(parentOf(x), BLACK);//把x的父節點的父節點設為紅色setColor(parentOf(parentOf(x)), RED);rotateLeft(parentOf(parentOf(x)));}}}//將根節點強制設置為黑色root.color = BLACK;
}
復制代碼

TreeMap的插入節點和普通的排序二叉樹沒啥區別,唯一不同的是,在TreeMap 插入節點后會調用方法fixAfterInsertion(e)來重新調整紅黑樹的結構來讓紅黑樹保持平衡。

我們重點關注下紅黑樹的fixAfterInsertion(e)方法,接下來我們來分別介紹兩種場景來演示fixAfterInsertion(e)方法的執行流程。

第一種場景:只需變色即可平衡

同樣是拿這顆紅黑樹舉例,現在我們插入節點 51。

c7e1ce62b2c69cef42e2e58512e79871.png

當我們需要插入節點51的時候,這個時候TreeMap 的 put 方法執行后會得到下面這張圖。

f05ead8362ff3f4b44c5f7fb67acd012.png

接著調用fixAfterInsertion(e)方法,如下代碼流程所示。

b147d3d7d7e69c458ac3992ad18b451c.png

當第一次進入循環后,執行后會得到下面的紅黑樹結構。

22f90e647e8ce84ec82585c756d46d36.png

在把 x 重新賦值后,重新進入 while 循環,此時的 x 節點為 45 。

1849333f287ece9f22b7a4e65ff4d1f4.png

執行上述流程后,得到下面所示的紅黑樹結構。

ef92e19ee8eb0d78cf96b84681b2870c.png

這個時候x被重新賦值為60,因為60是根節點,所以會退出 while 循環。在退出循序后,會再次把根節點設置為黑色,得到最終的結構如下圖所示。

2b8e05495ce4da61f12de0fd1b2cc238.png

最后經過兩次執行while循環后,我們的紅黑樹會調整成現在這樣的結構,這樣的紅黑樹結構是平衡的,所以路徑的黑高一致,并且沒有紅色節點相連的情況。

第二種場景 旋轉搭配變色來保持平衡

接下來我們再來演示第二種場景,需要結合變色和旋轉一起來保持平衡。

給定下面這樣一顆紅黑樹:

5d8b1f2d2c4f6e4b9abfae5ddda6fabb.png

現在我們插入節點66,得到如下樹結構。

f60d46ba247e4715731e044802e3c082.png

同樣地,我們進入fixAfterInsertion(e)方法。

76f3bf72c7f549c940363693f4ba96ca.png

e4a92b3d5d968a8494ca510f34774afb.png

最終我們得到的紅黑樹結構如下圖所示:

7893e61223beaba4f737895870e76f53.png

調整成這樣的結構我們的紅黑樹又再次保持平衡了。

演示 TreeMap 的流程就拿這兩種場景舉例了,其他的就不一一舉例了。

紅黑樹的刪除

因為之前的分享只整理了紅黑樹的插入部分,本來想著紅黑樹的刪除就不整理了,有人跟我反饋說紅黑樹的刪除相對更復雜,于是索性還是把紅黑樹的刪除再整理下。

刪除相對插入來說,的確是要復雜一點,但是復雜的地方是因為在刪除節點的這個操作情況有很多種,但是插入不一樣,插入節點的時候實際上這個節點的位置是確定的,在節點插入成功后只需要調整紅黑樹的平衡就可以了。

但是刪除不一樣的是,刪除節點的時候我們不能簡單地把這個節點設置為null,因為如果這個節點有子節點的情況下,不能簡單地把當前刪除的節點設置為null,這個被刪除的節點的位置需要有新的節點來填補。這樣一來,需要分多種情況來處理了。

刪除節點是根節點

直接刪除根節點即可。

刪掉節點的左子節點和右子節點都是為空

直接刪除當前節點即可。

刪除節點有一個子節點不為空

這個時候需要使用子節點來代替當前需要刪除的節點,然后再把子節點刪除即可。

給定下面這棵樹,當我們需要刪除節點69的時候。

f6930f6f0443495543c95f5587b342fd.png

首先用子節點代替當前待刪除節點,然后再把子節點刪除。

b2992027961dc9f0a92997629414b382.png

最終的紅黑樹結構如下面所示,這個結構的紅黑樹我們是不需要通過變色+旋轉來保持紅黑樹的平衡了,因為將子節點刪除后樹已經是平衡的了。

e4d90146ba4a2831b957c3f3a1af92ca.png

還有一種場景是當我們待刪除節點是黑色的,黑色的節點被刪除后,樹的黑高就會出現不一致的情況,這個時候就需要重新調整結構。

還是拿上面這顆刪除節點后的紅黑樹舉例,我們現在需要刪除節點67。

68625a2fc441b47fd0cb919b4c34ca8b.png

因為67 這個節點的兩個子節點都是null,所以直接刪除,得到如下圖所示結構:

d959827d4bc2adb532565d7c368aa29a.png

這個時候我們樹的黑高是不一致的,左邊黑高是3,右邊是2,所以我們需要把64節點設置為紅色來保持平衡。

df7893511a74cb26c8e05b4a106bdd05.png

刪除節點兩個子節點都不為空

刪除節點兩個子節點都不為空的情況下,跟上面有一個節點不為空的情況下也是有點類似,同樣是需要找能替代當前節點的節點,找到后,把能替代刪除節點值復制過來,然后再把替代節點刪除掉。

  • 先找到替代節點,也就是前驅節點或者后繼節點
  • 然后把前驅節點或者后繼節點復制到當前待刪除節點的位置,然后在刪除前驅節點或者后繼節點。

那么什么叫做前驅,什么叫做后繼呢? 前驅是左子樹中最大的節點,后繼則是右子樹中最小的節點。

前驅或者后繼都是最接近當前節點的節點,當我們需要刪除當前節點的時候,也就是找到能替代當前節點的節點,能夠替代當前節點肯定是最接近當前節點。

在當前刪除節點兩個子節點不為空的場景下,我們需要再進行細分,主要分為以下三種情況。

第一種,前驅節點為黑色節點,同時有一個非空節點

如下面這樣一棵樹,我們需要刪除節點64:

04c1dc30dcede240dbc39e8cec0dba44.png

首先找到前驅節點,把前驅節點復制到當前節點:

77e062ba662cbd111b36b5d8c1ff7cbd.png

接著刪除前驅節點。

4138c8b7dd2c41971b05a1fd131ce66e.png

這個時候63和60這個節點都是紅色的,我們嘗試把60這個節點設置為紅色即可使整個紅黑樹達到平衡。

0944bebc959296f20688a1dde81c4003.png

第二種,前驅節點為黑色節點,同時子節點都為空

前驅節點是黑色的,子節點都為空,這個時候操作步驟與上面基本類似。

如下操作步驟:

86cbc0e6ad93f20aaec374818573e783.png

因為要刪除節點64,接著找到前驅節點63,把63節點復制到當前位置,然后將前驅節點63刪除掉,變色后出現黑高不一致的情況下,最后把63節點設置為黑色,把65節點設置為紅色,這樣就能保證紅黑樹的平衡。

第三種,前驅節點為紅色節點,同時子節點都為空

給定下面這顆紅黑樹,我們需要刪除節點64的時候。

f61ceb0bfd17dad17ed8ea2eea65e1dd.png

同樣地,我們找到64的前驅節點63,接著把63賦值到64這個位置。

3b753f81d7f1dbe85ae7e177eca43de8.png

然后刪除前驅節點。

8f28989b10f0661e0def33239d31ebcf.png

刪除節點后不需要變色也不需要旋轉即可保持樹的平衡。

終于把紅黑樹的基本原理部分寫完了,用了很多示意圖,這篇文章是在之前分享的 ppt 上再整理出來,我覺得自己應該算是把基本操作講明白了,整理這篇文章前前后后用了近一周左右,因為平時上班,基本上只有周末有時間才有時間整理,如有問題請留言討論。

如果您覺得寫得還可以,請您幫忙點個贊,您的點贊真的是對我最大的支持,也是我能繼續寫下去的動力,感謝。

轉自:https://juejin.im/post/5df4aa...

怒求一波贊

能堅持看到這兒的都是努力學習的人,我們相信,努力奮斗終將會使我們過上自己想要的生活。

我會努力更新原創干貨,也會收集一些精品文章,供大家日常學習。不論如何,如果大家覺得在我這兒能學到點東西,在這兒厚著臉皮的向大家求個贊,求個關注,求個分享。我一定不會辜負大家,為大家的學習之路添加更多精彩的文章。

創作不易,堅持不易,大家的支持是我最大的動力,再次謝謝大家。

下面這篇文章,是我收集的5000G的精品VIP視頻的部分目錄,都是會免費分享給大家的,大家可以點進去看看是否有自己需要的,如果沒有,大家也可以通過公眾號或者微信私聊我,我也會盡力去收集。

java架構師:作為Java開發,我是如何在一年之內,讓自己的月薪爆炸式提升!!?zhuanlan.zhihu.com
87e5f072a0980c7692b7ba5915b6b483.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/538831.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/538831.shtml
英文地址,請注明出處:http://en.pswp.cn/news/538831.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux 父子進程結束,Linux下讓父進程結束后,子進程自動結束

在多進程編程的時候&#xff0c;經常會遇到這樣的情況。父進程創建了一堆子進程&#xff0c;當遇到錯誤或者操作失誤的時候把父進程關閉了&#xff0c;但是子進程還在跑&#xff0c;不得不一個一個地殺死子進程&#xff0c;或者使用ps,grep,awk,kill來配合批量殺死。之前在寫 x…

android:showAsAction 無效

我想要的效果 但actionbar上的搜索菜單不顯示 在androidstudio里&#xff0c;android:showAsAction"always"標紅 根據提示&#xff0c;需要加入 xmlns:app"http://schemas.android.com/apk/res-auto" 加入后依然無效 正確的加入方式是&#xff1a;

Exchange_Server_2013在Windows_2008_R2部署

Exchange Server 2013可以部署在Windows Server 2012的平臺&#xff0c;也可以部署在Windows Server 2008 R2的平臺。如果部署在Windows Server 2008 R2平臺要求操作系統版本為Windows Server 2008 R2 SP1的版本。如下拓撲圖&#xff1a;在本架構中有兩臺服務器&#xff0c;都安…

建立副本名稱沖突_包的建立(一)

這次的內容&#xff0c;涉及到 R 語言包的建立。事實上&#xff0c;CRAN 提供的官方參考指南&#xff0c;并不適合快速閱讀&#xff0c;且內容繁雜。比較適合作為后期提高的 教材。而 http://r-pkgs.had.co.nz/ 上 的教程則更適合作為 R 包編寫的幫助指南。這里&#xff0c;僅僅…

Android 多選列表

原文&#xff1a;http://blog.csdn.net/wljun739/article/details/37655209 點擊閱讀原文 ----------------------------------------------------------- 1、activity_main.xml[java] view plaincopy<LinearLayout xmlns:android"http://schemas.android.com/apk/res/…

python自帶的編輯器怎么換行_Python3基礎 print 自帶換行功能

鎮場詩&#xff1a; ———大夢誰覺&#xff0c;水月中建博客。百千磨難&#xff0c;才知世事無常。 ———今持佛語&#xff0c;技術無量愿學。愿盡所學&#xff0c;鑄一良心博客。 —————————————————————————————————————————— 1 …

查看db2數據庫名linux,【名說】DB2數據庫備份與恢復(linux環境)

lslinux 下備份db2數據庫1.SSH方式&#xff1a;登錄db2數據庫(因為是linux環境 &#xff0c; putty就不錯)2.進入備份文件夾&#xff1a;cd /home/backup/db2 list application | grep 數據庫名//(可能會有一些連接進程&#xff0c;有則全部殺掉)//殺進程&#xff1a;db2 "…

leetcode 回文數

2019獨角獸企業重金招聘Python工程師標準>>> 判斷一個整數是否是回文數。回文數是指正序&#xff08;從左向右&#xff09;和倒序&#xff08;從右向左&#xff09;讀都是一樣的整數。 示例 1: 輸入: 121 輸出: true 示例 2: 輸入: -121 輸出: false 解釋: 從左向右…

安裝ae顯示安裝程序無法初始化_adobe CC 2015/2017安裝失敗(adobe cc安裝不了的解決辦法)...

adobe CC 2015/2017安裝失敗(adobe cc安裝不了的解決辦法)書法字體2015.06.18Adobe Application ManagerAdobe Creative Cloud 2015/2017全系統軟件已經可以從官網下載了&#xff0c;相信又將有一大波設計師會更新安裝adobe CC 2015/2017軟件。本著嘗鮮的精神&#xff0c;本人也…

Hadoop控制輸出文件命名

原文地址&#xff1a;http://blog.csdn.net/zuochanxiaoheshang/article/details/8769198 點擊閱讀原文 --------------------------------------------------- Hadoop 控制輸出文件命名 在一般情況下&#xff0c;Hadoop 每一個 Reducer 產生一個輸出文件&#xff0c;文件以 …

office高級應用與python綜合案例教程_office高級應用與python綜合案例實驗指導--詳細介紹...

隨著社會經濟的發展&#xff0c;現代信息技術逐漸改變著人們的工作和生活方式。為使學生掌握辦公自動化軟件高級應用的技能&#xff0c;了解Python程序基礎知識&#xff0c;綜合運用辦公自動化軟件分析和解決實際問題&#xff0c;編者編寫了本書。 本書圍繞高等學校培養應用型人…

linux系統的安全機制有哪些內容,系統安全機制

AG351.SELINUXSElinux 是一個強制訪問控制系統,它為每個進程與文件都打上一個安全上下文標簽,而 selinux 通過這個標簽對系統訪問控制進行管理。2.針對車載產品對于啟動安全、平臺運行安全、通信安全三個主要領域有著特 殊 很 高 的 要 求 , 為 此 Quectel 結 合 了 Qualcomm 給…

移動端video播放時不彈出頁面層

移動端視頻在播放時會主動彈出頁面&#xff0c;有的瀏覽器不會。對那些會的瀏覽器進行處理&#xff1a; 直接加上下面三個屬性即可&#xff0c;兼容方面就不說了&#xff0c;微信上是很ok的。 <video x5-playsinline"" playsinline"" webkit-playsinlin…

1.計算機語言發展史

第一代 計算機語言 第二代 匯編語言 第三代 高級語言 面向過程&#xff1a;c&#xff0c;fortan&#xff0c;cobol&#xff0c;pascal&#xff0c;ada 面向對象&#xff1a;c&#xff0c;java&#xff0c;c# 計算機語言&#xff1a; 01010100010111000 010101010000 00…

定題信息服務是從什么角度_信息管理練習題2

1.文件的目錄結構是網頁在服務器上的存放狀況。(對)2、網絡信息指引庫存放的是有關主題的數據庫或服務器地址。(對)3、數據庫組織方式是將超文本與多媒體技術結合起來的組織方式。(錯)4、按信息的組織方式劃分&#xff0c;搜索引擎則可以分為目錄式搜索引擎(Yahoo)、索引式搜索…

python判斷是否為完全數_Python識別完美數

完美數 完美數(perfect number&#xff0c;又稱完全數)指&#xff0c;它所有的真因子(即除了自身以外的因子)和&#xff0c;恰好等于它自身。 第一個完美數&#xff1a;6&#xff0c; 第二個完美數&#xff1a;28&#xff0c; 第三個完美數&#xff1a;496&#xff0c; 第四個完…

linux嵌入式做智能家居,嵌入式系統在智能家居中的應用

汪家樂利用嵌入式系統來構建智能家居系統&#xff0c;使得用戶可以根據實際需求來進行操作&#xff0c;不僅可以提高生活水平&#xff0c;并且與其他系統相比&#xff0c;其在運行上具有更高的穩定性。本文對嵌入式系統在智能家居中應用要點進行了簡單分析。【關鍵詞】嵌入式系…

前端路由的兩種實現原理

2019獨角獸企業重金招聘Python工程師標準>>> History API 這里不細說每一個 API 的用法&#xff0c;大家可以看 MDN 的文檔&#xff1a;https://developer.mozilla.org... 重點說其中的兩個新增的API history.pushState 和 history.replaceState 這兩個 API 都接收三…

2.JAVA簡史

SUN公司 --美國SUN&#xff08;Stanford university network&#xff09;公司 --在中國大陸的正式中文名&#xff1a;太陽計算機系統&#xff08;中國&#xff09;有限公司 --在中國臺灣中文名&#xff1a;升陽電腦公司 JAVA為什么被發明&#xff1f; --是sun公司Green項目…

es統計有多少個分組_ES 24 - 如何通過Elasticsearch進行聚合檢索 (分組統計)

1 普通聚合分析1.1 直接聚合統計(1) 計算每個tag下的文檔數量, 請求語法:GET book_shop/it_book/_search{"size": 0, // 不顯示命中(hits)的所有文檔信息"aggs": {"group_by_tags": {// 聚合結果的名稱, 需要自定義(復制時請去掉此注釋)"te…