二叉樹結構及複雜度

來源:互聯網
上載者:User

標籤:des   style   blog   http   color   使用   

在上一篇文章《常用資料結構及複雜度》中,介紹了一些電腦程式設計中常用的線性資料結構,包括 Array、ArrayList、LinkedList<T>、List<T>、Stack<T>、Queue<T>、Hashtable 和 Dictionary<T> 等。並簡單介紹了這些資料結構的內部實現原理和常用操作的運算複雜度,以及如何選擇合適的資料結構。本篇文章中,我們將介紹常見的樹形結構及其常用操作的運算複雜度。

我們知道像家譜族譜、公司組織圖等都是以樹結構的形式組織資料。例如中所展示的公司組織圖。

樹(Tree)是由多個節點(Node)的集合組成,每個節點又有多個與其關聯的子節點(Child Node)。子節點就是直接處於節點之下的節點,而父節點(Parent Node)則位於節點直接關聯的上方。樹的根(Root)指的是一個沒有父節點的單獨的節點。

所有的樹都呈現了一些共有的特性:

  1. 只有一個根節點;
  2. 除了根節點,所有其他節點都有且只有一個父節點;
  3. 無環。將任意一個節點作為起始節點,都不存在任何回到該起始節點的路徑。(正是前兩個特性保證了無環的成立。)
二叉樹(Binary Tree)

二叉樹(Binary Tree)是一種特殊的樹類型,其每個節點最多隻能有兩個子節點。這兩個子節點分別稱為當前節點的左孩子(left child)和右孩子(right child)。

中,二叉樹(a)包含 8 個節點,其中節點 1 是它的根節點。節點 1 的左孩子為節點 2,右孩子為節點 3。注意,並沒有要求一個節點同時具有左孩子和右孩子。例如,二叉樹(a)中,節點 4 就只有一個右孩子 6。此外,節點也可以沒有孩子節點。例如,二叉樹(b)中,節點 4、5、6、7 都沒有孩子節點。

沒有孩子的節點稱為分葉節點(Leaf Node),有孩子的節點則稱為內節點(Internal Node)。如中,二叉樹 (a) 中節點 6、8 為分葉節點,節點 1、2、3、4、5、7 為內節點。

.NET 中並沒有直接提供二叉樹的實現,我們需要自己來實現 BinaryTree 類。在《你曾實現過二叉樹嗎》一文中,實現了一個基於泛型的簡單的二叉樹模型。

我們已經瞭解了數組是將元素連續地排列在記憶體當中,而二叉樹卻不是採用連續的記憶體存放。實際上,通常 BinaryTree 類的執行個體僅包含根節點(Root Node)執行個體的引用,而根節點執行個體又分別指向它的左右孩子節點執行個體,以此類推。所以關鍵的不同之處在於,組成二叉樹的節點對象執行個體可以分散到 CLR 託管堆中的任何位置,它們沒有必要像數組元素那樣連續的存放。

如果要訪問二叉樹中的某一個節點,通常需要逐個遍曆二叉樹中的節點,來定位那個節點。它不象數組那樣能對指定的節點進行直接的訪問。所以尋找二叉樹的漸進時間是線性 O(n),在最壞的情況下需要尋找樹中所有的節點。也就是說,隨著二叉樹節點數量增加時,尋找任一節點的步驟數量也將相應地增加。

那麼,如果一個二叉樹的尋找時間是線性,定位時間也是線性,那相比數組來說到底哪裡有優勢呢?畢竟數組的尋找時間雖然是線性 O(n),但定位時間卻是常量 O(1) 啊?的確是這樣,通常來說普通的二叉樹確實不能提供比數組更好的效能。然而,如果我們按照一定的規則來組織排列二叉樹中的元素時,就可以很大程度地改善查詢時間和定位時間。

二叉尋找樹(Binary Search Tree)

二叉尋找樹(BST:Binary Search Tree)是一種特殊的二叉樹,它改善了二叉樹節點尋找的效率。二叉尋找樹有以下特性:

對於任意一個節點 n,

  • 其左子樹(left subtree)下的每個後代節點(descendant node)的值都小於節點 n 的值;
  • 其右子樹(right subtree)下的每個後代節點的值都大於節點 n 的值。

所謂節點 n 的子樹,可以將其看作是以節點 n 為根節點的樹。子樹的所有節點都是節點 n 的後代,而子樹的根則是節點 n 本身。

中展示了兩個二叉樹。二叉樹(b)是一個二叉尋找樹(BST),它符合二叉尋找樹的特性規定。而二叉樹(a),則不是二叉尋找樹。因為節點 10 的右孩子節點 8 小於節點 10,但卻出現在節點 10 的右子樹中。同樣,節點 8 的右孩子節點 4 小於節點 8,但出現在了它的右子樹中。無論是在哪個位置,只要不符合二叉尋找樹的特性規定,就不是二叉尋找樹。例如,節點 9 的左子樹只能包含值小於節點 9 的節點,也就是 8 和 4。

從二叉尋找樹的特性可知,BST 各節點儲存的資料必須能夠與其他的節點進行比較。給定任意兩個節點,BST 必須能夠判斷這兩個節點的值是小於、大於還是等於。

假設我們要尋找 BST 中的某一個節點。例如在中的二叉尋找樹(b)中,我們要尋找值為 10 的節點。

我們從根開始尋找。可以看到,根節點的值為 7,小於我們要尋找的節點值 10。因此,如果節點 10 存在,必然存在於其右子樹中,所以應該跳到節點 11 繼續尋找。此時,節點值 10 小於節點 11 的值,則節點 10 必然存在於節點 11 的左子樹中。在尋找節點 11 的左孩子,此時我們已經找到了目標節點 10,定位於此。

如果我們要尋找的節點在樹中不存在呢?例如,我們要尋找節點 9。重複上述操作,直到到達節點 10,它大於節點 9,那麼如果節點 9 存在,必然存在於節點 10 的左子樹中。然而我們看到節點 10 根本就沒有左孩子,因此節點 9 在樹中不存在。

總結來說,我們使用的尋找演算法過程如下:

假設我們要尋找節點 n,從 BST 的根節點開始。演算法不斷地比較節點值的大小直到找到該節點,或者判定不存在。每一步我們都要處理兩個節點:樹中的一個節點,稱為節點 c,和要尋找的節點 n,然後並比較 c 和 n 的值。開始時,節點 c 為 BST 的根節點。然後執行以下步驟:

  1. 如果 c 值為空白,則 n 不在 BST 中;
  2. 比較 c 和 n 的值;
  3. 如果值相同,則找到了指定節點 n;
  4. 如果 n 的值小於 c,那麼如果 n 存在,必然在 c 的左子樹中。回到第 1 步,將 c 的左孩子作為 c;
  5. 如果 n 的值大於 c,那麼如果 n 存在,必然在 c 的右子樹中。回到第 1 步,將 c 的右孩子作為 c;

通過 BST 尋找節點,理想情況下我們需要檢查的節點數可以減半。如中的 BST 樹,包含了 15 個節點。從根節點開始執行尋找演算法,第一次比較決定我們是移向左子樹還是右子樹。對於任意一種情況,一旦執行這一步,我們需要訪問的節點數就減少了一半,從 15 降到了 7。同樣,下一步訪問的節點也減少了一半,從 7 降到了 3,以此類推。

根據這一特點,尋找演算法的時間複雜度應該是 O(log­2n),簡寫為 O(lg n)。我們在文章《演算法複雜度分析》中有一些關於時間複雜度的描述。可知,log­2n = y,相當於 2y = n。即,如果節點數量增加 n,尋找時間只緩慢地增加到 log­2n。中顯示了 O(log­2n) 和線性增長 O(n) 的增長率之間的區別。時間複雜度為 O(log­2n) 的演算法已耗用時間為下面那條線。

從可以看出,O(log­2n) 曲線幾乎是水平的,隨著 n 值的增加,曲線增長十分緩慢。舉例來說,尋找一個具有 1000 個元素的數組,需要查詢 1000 個元素,而尋找一個具有 1000 個元素的 BST 樹,僅需查詢不到10 個節點(log21024 = 10)。

而實際上,對於 BST 尋找演算法來說,其十分依賴於樹中節點的拓撲結構,也就是節點間的布局關係。描繪了一個節點插入順序為 20, 50, 90, 150, 175, 200 的 BST 樹。這些節點是按照遞升順序被插入的,結果就是這棵樹沒有廣度(Breadth)可言。也就是說,它的拓撲結構其實就是將節點排布在一條線上,而不是以扇形結構散開,所以尋找時間也為 O(n)。

當 BST 樹中的節點以扇形結構散開時,對它的插入、刪除和尋找操作最優的情況下可以達到亞線性已耗用時間 O(log2n)。因為當在 BST 中尋找一個節點時,每一步比較操作後都會將節點的數量減少一半。儘管如此,如果拓撲結構像中的樣子時,已耗用時間就會退減到線性時間 O(n)。因為每一步比較操作後還是需要逐個比較其餘的節點。也就是說,在這種情況下,在 BST 中尋找節點與在數組(Array)中尋找就基本類似了。

因此,BST 演算法尋找時間依賴於樹的拓撲結構。最佳情況是 O(log­2n),而最壞情況是 O(n)。

插入節點

我們不僅需要瞭解如何在二叉尋找樹中尋找一個節點,還需要知道如何在二叉尋找樹中插入和刪除一個節點。

當向樹中插入一個新的節點時,該節點將總是作為葉子節點。所以,最困難的地方就是如何找到該節點的父節點。類似於尋找演算法中的描述,我們將這個新的節點稱為節點 n,而遍曆的當前節點稱為節點 c。開始時,節點 c 為 BST 的根節點。則定位節點 n 父節點的步驟如下:

  1. 如果節點 c 為空白,則節點 c 的父節點將作為節點 n 的父節點。如果節點 n 的值小於該父節點的值,則節點 n 將作為該父節點的左孩子;否則節點 n 將作為該父節點的右孩子。
  2. 比較節點 c 與節點 n 的值。
  3. 如果節點 c 的值與節點 n 的值相等,則說明使用者在試圖插入一個重複的節點。解決辦法可以是直接丟棄節點 n,或者可以拋出異常。
  4. 如果節點 n 的值小於節點 c 的值,則說明節點 n 一定是在節點 c 的左子樹中。則將父節點設定為節點 c,並將節點 c 設定為節點 c 的左孩子,然後返回至第 1 步。
  5. 如果節點 n 的值大於節點 c 的值,則說明節點 n 一定是在節點 c 的右子樹中。則將父節點設定為節點 c,並將節點 c 設定為節點 c 的右孩子,然後返回至第 1 步。

當合適的節點找到時,該演算法結束。從而使新節點被放入 BST 中成為某一父節點合適的孩子節點。

BST 的插入演算法的複雜度與尋找演算法的複雜度是一樣的:最佳情況是 O(log­2n),而最壞情況是 O(n)。因為它們對節點的尋找定位策略是相同的。

刪除節點

從 BST 中刪除節點比插入節點難度更大。因為刪除一個非葉子節點,就必須選擇其他節點來填補因刪除節點所造成的樹的斷裂。如果不選擇節點來填補這個斷裂,那麼就違背了 BST 的特性要求。

刪除節點演算法的第一步是定位要被刪除的節點,這可以使用前面介紹的尋找演算法,因此已耗用時間為 O(log­2n)。接著應該選擇合適的節點來代替刪除節點的位置,它共有三種情況需要考慮。

  • 情況 1:如果刪除的節點沒有右孩子,那麼就選擇它的左孩子來代替原來的節點。二叉尋找樹的特性保證了被刪除節點的左子樹必然符合二叉尋找樹的特性。因此左子樹的值要麼都大於,要麼都小於被刪除節點的父節點的值,這取決於被刪除節點是左孩子還是右孩子。因此用被刪除節點的左子樹來替代被刪除節點,是完全符合二叉搜尋樹的特性的。
  • 情況 2:如果被刪除節點的右孩子沒有左孩子,那麼這個右孩子被用來替換被刪除節點。因為被刪除節點的右孩子都大於被刪除節點左子樹的所有節點,同時也大於或小於被刪除節點的父節點,這同樣取決於被刪除節點是左孩子還是右孩子。因此,用右孩子來替換被刪除節點,符合二叉尋找樹的特性。
  • 情況 3:如果被刪除節點的右孩子有左孩子,就需要用被刪除節點右孩子的左子樹中的最下面的節點來替換它,就是說,我們用被刪除節點的右子樹中最小值的節點來替換。


我們知道,在 BST 中,最小值的節點總是在最左邊,最大值的節點總是在最右邊。因此替換被刪除節點右子樹中最小的一個節點,就保證了該節點一定大於被刪除節點左子樹的所有節點。同時,也保證它替代了被刪除節點的位置後,它的右子樹的所有節點值都大於它。因此這種選擇策略符合二叉尋找樹的特性。

和尋找、插入演算法類似,刪除演算法的已耗用時間也與 BST 的拓撲結構有關,最佳情況是 O(log­2n),而最壞情況是 O(n)。

節點遍曆

對於線性連續的數組來說,遍曆數組採用的是單向的迭代法。從第一個元素開始,依次向後迭代每個元素。而 BST 則有三種常用的遍曆方式:

  • 前序走訪(Perorder traversal)
  • 中序遍曆(Inorder traversal)
  • 後序遍曆(Postorder traversal)

當然,這三種遍曆方式的工作原理是類似的。它們都是從根節點開始,然後訪問其子節點。區別在於遍曆時,訪問節點本身和其子節點的順序不同。

前序走訪(Perorder traversal)

前序走訪從當前節點(節點 c)開始訪問,然後訪問其左孩子,再訪問右孩子。開始時,節點 c 為 BST 的根節點。演算法如下:

  1. 訪問節點 c;
  2. 對節點 c 的左孩子重複第 1 步;
  3. 對節點 c 的右孩子重複第 1 步;

則中樹的遍曆結果為:90, 50, 20, 5, 25, 75, 66, 80, 150, 95, 92, 111, 175, 166, 200。

中序遍曆(Inorder traversal)

中序遍曆是從當前節點(節點 c)的左孩子開始訪問,再訪問當前節點,最後是其右節點。開始時,節點 c 為 BST 的根節點。演算法如下:

  1. 訪問節點 c 的左孩子;
  2. 對節點 c 重複第 1 步;
  3. 對節點 c 的右孩子重複第 1 步。

則中樹的遍曆結果為:5, 20, 25, 50, 66, 75, 80, 90, 92, 95, 111, 150, 166, 175, 200。

後序遍曆(Postorder traversal)

後序遍曆首先從當前節點(節點 c)的左孩子開始訪問,然後是右孩子,最後才是當前節點本身。開始時,節點 c 為 BST 的根節點。演算法如下:

  1. 訪問節點 c 的左孩子;
  2. 對節點 c 的右孩子重複第1 步;
  3. 對節點 c 重複第 1 步;

則中樹的遍曆結果為:5, 25, 20, 66, 80, 75, 50, 92, 111, 95, 166, 200, 175, 150, 90。

樹的高度

實際上,BST 操作的已耗用時間與樹的高度(Height)是有關係的。一個樹的高度指的是從樹的根開始所能到達的最長的路徑長度。樹的高度可被遞迴性地定義為:

  • 如果節點沒有子節點,則高度為 0;
  • 如果節點只有一個子節點,則高度為該子節點的高度加 1;
  • 如果節點有兩個子節點,則高度為兩個子節點中高度較高的加 1;

計算樹的高度要從葉子節點開始,首先將葉子節點的高度置為 0,然後根據上面的規則向上計算父節點的高度。以此類推直到樹中所有的節點高度都被標註後,則根節點的高度就是樹的高度。

顯示了幾棵已經計算好高度的 BST 樹。

如果樹中節點的數量為 n,則一棵滿足O(log2n) 漸進已耗用時間的 BST 樹的高度應接近於比 log2n 小的最大整數。

中的三棵 BST 樹中,樹(b)擁有最好的高度與節點數量的比例。樹(b)的高度為 3 ,節點數量為 8,所以 log28 = 3,結果正好與樹的高度相等。

樹(a)的節點數量為 10,而高度為 4,log210 = 3.3219,比 3.3219 小的最大整數是 3,所以樹(a)最理想的高度應該為 3。我們可以通過移動距離最遠的節點到中間的某個非葉子節點,以減少數的高度,以使該樹的高度與節點數量的比例達到最優。

樹(c)的情況是最差的,它的節點數量是 5,所以log25 = 2.3219,則理想高度為 2,但實際上是 4。

實際上我們真正面對的問題是如何保證 BST 的拓撲結構始終保持樹高度與節點數量的最佳比例。因為 BST 的拓撲結構與節點的插入順序息息相關,一種方式是通過資料的亂序來保證。如果在向樹中插入節點前就可以得到資料還好說,而如果我們無法掌控資料的來源呢?比如,資料來自使用者的輸入,或者來自感應器的即時資料等,基本上要保證資料亂序是沒希望了。那麼,另一種方案就是在不試圖讓資料來源決定資料順序的情況下,新的節點插入後仍然可以保持 BST 樹的平衡(balanced)。這種能夠始終維持樹平衡狀態的資料結構稱為自平衡二叉尋找樹(self-balancing binary search tree)。

一棵平衡樹指的是樹能夠保持其高度與廣度能夠保持預先定義的比例。不同的資料結構可以定義不同的比例以保持平衡,但所有的比例都趨向於log2n。那麼,一顆自平衡的 BST 也同樣呈現出 O(log2n) 的漸進已耗用時間。

有許多種不同的自平衡 BST 資料結構,例如 AVL 樹、紅/黑樹狀結構(Red-Black Tree)、2-3 樹、2-3-4 樹、伸展樹(Splay Tree)、B 樹等等。本文中我們將簡要介紹其中的兩種:AVL 樹和紅/黑樹狀結構。

AVL 樹

在 1962 年,俄羅斯數學家 G. M. Andel‘son-Vel-skii 和 E. M. Landis 發明了第一種自平衡二叉尋找樹,叫做 AVL 樹。AVL 樹必須維持如下平衡條件,對每個節點 n:

  • 節點 n 的左子樹的高度與右子樹的高度的差至多是 1。

節點的左子樹或者右子樹的高度可以通過上面描述的步驟來計算。如果節點僅有一個子節點,則無子節點側的高度為 -1。

展示了概念上 AVL 樹節點的兩側子樹高度需要保持的關係。

下面是一些 BST 樹。節點中的數字代表著節點的值,左右兩側的數字代表著左右子樹的高度。其中樹(a)和樹(b)是合法的 AVL 樹,而樹(c)和樹(d)則不合法,因為樹中不是所有的節點都滿足 AVL 的平衡特性要求。

當建立一棵 AVL 樹時,痛點在於如何保證 AVL 的平衡特性要求,而不用關注對樹的具體操作。也就是說,無論是向樹添加節點還是刪除節點,最重要的事情就是保持樹的平衡。AVL 樹通過 "旋轉操作(rotations)" 來保持樹的平衡。旋轉操作可以重塑樹的拓撲結構來恢複樹的平衡,更重要的是,重塑後的樹依然符合二叉尋找樹的特性要求。

當向一棵 AVL 樹中插入一個新的節點時,需要經過兩階段的過程。首先,插入新節點的操作將使用與向 BST 樹中插入新節點時使用的相同的尋找演算法。新的節點將做為一個葉子節點被添加到樹中合適的位置,以滿足 BST 的特性要求。在添加完節點後,將導致樹的結構可能已經違背 AVL 樹的特性要求。所以在第二個階段中,將遍曆訪問路徑,來檢查每個節點左右子樹高度。如果存在某節點的左右子樹的高度差大於 1 時,則需要使用旋轉操作來處理。

闡述了對節點 3 進行旋轉操作的步驟。在階段一插入新節點 2 後,在節點 5 處的 AVL 樹的平衡特性已經被破壞,因為節點 5 的左右子樹的差為 2,大於 AVL 樹要求的 1。為瞭解決這個問題,需要在節點 5 的左子樹的根節點,也就是節點 3 處做旋轉操作。這個旋轉操作不僅恢複了 AVL 樹的平衡要求,而且也保持了 BST 的特性要求。

有時除了像中描述的簡單的旋轉操作之外,可能還需要進行多次旋轉操作。對於成組的旋轉操作的深入討論已經超出了本篇文章的範疇,這裡就不再贅述了。最重要的就是要意識到插入操作和刪除操作都會破壞 AVL 樹的平衡,而旋轉操作就是解決這些問題的法寶。

通過確保所有節點的左右子樹的差小於等於 1,AVL 樹保證了插入、刪除和尋找操作將始終保持 O(log2n) 的漸進已耗用時間,而與插入或刪除節點的順序無關。

紅/黑樹狀結構(Red-Black Tree)

在 1972 年,慕尼黑理工大學(Technical University of Munich)的電腦科學家 Rudolf Bayer 創造了紅/黑樹狀結構(Red-Black Tree)資料結構。除了包含資料和左右孩子節點之外,紅/黑樹狀結構的節點還包含了一項特別的資訊 -- 顏色。這個顏色只包含兩種顏色,即紅色和黑色。並且,紅/黑樹狀結構還添加了一種特殊類型的節點,稱為 NIL 節點。NIL 節點將做為紅/黑樹狀結構的偽葉子節點出現。也就是說,所有帶有關鍵資料的節點稱為內節點,而所有其他的外節點則均指向 NIL 節點。這個概念可能理解起來有些費勁,希望下面這張圖有所協助。

紅/黑樹狀結構(R-B Tree)需要滿足如下特性:

  1. 節點的顏色只能是紅色或者黑色;
  2. 根節點是黑色的;(根特性
  3. NIL 節點的顏色是黑色;
  4. 如果節點的顏色是紅色,則其子節點均為黑色;(紅特性
  5. 從任一節點到其後代任一葉子節點的路徑上的黑色節點的數量相同;(黑特性

前面幾條特性都很好解釋,只有最後一條最難理解。簡單的說,從樹中任意一個節點開始,從該節點到其後代的任意一個 NIL 節點的路徑上的黑色節點的數量必須相同。比如中,以根節點為例,從節點 41 到任意一個 NIL 節點的路徑上,黑色節點的數量都是相同的,也就是 3 個。如從節點 41 到左下角的 NIL 節點的路徑上,黑色節點包括 41, 2, NIL,所以黑色節點數量是 3 個。

類似於 AVL 樹,紅/黑樹狀結構也是一種自平衡二叉尋找樹。AVL 樹的平衡特性是通過限制節點的左右子樹的高度來達成,而紅/黑樹狀結構則是通過更形象化的方式來保證樹的平衡。如果一棵樹滿足紅/黑樹狀結構的特性,其節點的總數量為 n,則它的高度將始終小於 2 * log2(n+1) 。鑒於這個原因,致使紅/黑樹狀結構保證了對樹的所有操作都能在 O(log2n) 漸進已耗用時間範圍內。

同樣是和 AVL 樹一樣,當對紅/黑樹狀結構進行節點的插入和刪除時,最終要的就是使其仍然符合紅/黑樹狀結構的特性。AVL 樹通過使用旋轉操作(rotations)來恢複樹的平衡。而紅/黑樹狀結構則是通過重新著色(recoloring)和旋轉兩種操作共同來完成。這不僅需要判斷節點的父節點的顏色,還需要對比叔父節點的顏色,使得紅/黑樹狀結構的恢複過程變得更加複雜。

向紅/黑樹狀結構中插入新的節點時,需要考慮很多種情況。假設已存在紅/黑樹狀結構 T,即將被插入的新節點為 K。

首先一種特殊情況就是如果樹 T 為空白,則可直接將節點 K 設定為根節點,並且將顏色標為黑色,這樣即可滿足 R-B 樹的所有要求。

如果樹 T 不為空白,則需要遵循如下步驟:

  1. 使用 BST 插入演算法將節點 K 插入到樹 T 中;
  2. 將節點 K 著色為紅色;
  3. 如果需要,則重塑 R-B 樹的特性;

我們知道 BST 樹總是將新節點添加為葉子節點,所以將節點 K 插入到樹 T 中不會破壞根特性。而添加一個紅色的葉子節點也不會影響樹 T 的黑特性。實際上,添加一個紅色的葉子節點僅有可能影響樹 T 的紅特性,所以我們僅需檢查樹的紅特性,如果紅特性被違背,則需要重塑樹結構以重新滿足紅/黑樹狀結構特性。

我們將節點 K 的父節點稱為節點 P(parent node),將節點 P 的父節點稱為節點 G(grandparent node),將節點 P 的兄弟節點稱為節點 S(sibling node)。

當向非空樹 T 中插入節點 K 時,將直接受到父節點 P 的顏色的影響,可能會遇到如下多種情況。

情況1:節點 P 是黑色。

如果 P 為黑色,而節點 K 為紅色,所以實際上不會違背紅特性,則樹 T 已經滿足所有紅/黑樹狀結構特性條件。

情況2:節點 P 是紅色。

如果節點 P 為紅色,那麼 P 現在有了新的子節點 K,並且 K 也為紅色,所以已經違背了紅特性。為了處理這種兩個紅節點的情況,我們需要考慮節點 G 的其他子節點,也就是節點 P 的兄弟節點 S。此時,會有兩種情況發生:

情況 2a:節點 S 是黑色或者為空白。

如果節點 S 是黑色或者為空白,則需要對節點 K、P、G 進行旋轉。根據 K、P、G 順序的不同,旋轉操作可能存在四種可能性。

前兩種可能性為當 P 為 G 的左子節點時。

如果 S 為空白,則中直接將 S 刪除即可。

另兩種可能性為當 P 為 G 的右子節點時,正好與上面圖中的過程相反。

旋轉操作過程結束後,雙紅節點情況已經被合理的解決了。

情況 2b:節點 S 是紅色。

如果 P 的兄弟節點 S 是紅色,則需要對 P、S、G 進行重新著色:將 P 和 S 著色為黑色,將 G 著色為紅色。

重新著色操作不會影響樹 T 的黑特性,因為當 P、G 的顏色更改時,所有路徑上的黑色節點數量並沒有改變。但是,重新著色可能會使 G 和 G 的父節點產生雙紅情況。這種情況下,則需要從 G 和 G 的父節點開始繼續遵循處理 K 和 K 的父節點的方式遞迴式地解決雙紅問題。

對於紅/黑樹狀結構深入的討論不在本文的範疇,這裡不再贅述。

參考資料

  • An Extensive Examination of Data Structures Using C# 2.0
  • 考察資料結構 - 第三部分:二叉樹和BSTs[譯]
  • Red–black tree
  • Red/Black Tree Algorithm Visualization
  • Left-Leaning Red-Black Trees
  • Red-Black Trees
  • Introduction to Algorithms : LECTURE 10 Balanced Search Trees 
  • 教你透徹瞭解紅/黑樹狀結構

本文《二叉樹結構及複雜度》由 Dennis Gao 發表自部落格園部落格,任何未經作者本人允許的人為或爬蟲轉載均為耍流氓。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.