Sedgewick的紅黑樹


 

  紅黑樹一直是數據結構中的難點,大部分關於算法與數據結構的學習資料(包括《算法導論》)對於這部分的講解都是上來就下定義,告訴我們紅黑樹這個性質那個性質,插入刪除要注意1234點,但是基本沒有講為什么這樣定義紅色和黑色,讓人理解起來十分費力。直到我看了下圖這本書中關於紅黑樹部分的講解,一時間豁然開朗,上網查了下這本書的作者Sedgewick,他是偉大的高德納的學生!紅黑樹的發明者!

  他在這本書中告訴了我們紅黑樹的根本模型:以二叉樹的形式實現2-3樹,通過紅黑樹與2-3樹之間的一一對應,讓我們對紅黑樹有了更直觀的理解。

  這本書里所講的是左偏紅黑樹模型,理解了這個模型,再理解算法導論的完整紅黑樹模型就容易的多了。

  

 

 

  2-3樹

  

  定義2-3查找樹允許樹中的一個結點保存多個鍵,一棵2-3查找樹或為一棵空樹,或由以下結點組成:

  2-結點,含有一個鍵(及其對應的值)和兩條鏈接,左鏈接指向的2-3樹中的鍵都小於該結點,右鏈接指向的2-3樹中的鍵都大於該結點。

  3-結點,含有兩個鍵(及其對應的值)和三條鏈接,左鏈接指向的2-3樹中的鍵都小於該結點,中鏈接指向的2-3樹中的鍵都位於該結點的兩個鍵之間,右鏈接指向的2-3樹中的鍵都大於該結點。

  一棵完美平衡的2-3查找樹中的所有空鏈接到根結點的距離都應該是相同的。這里我們用2-3樹指代一棵完美平衡的2-3查找樹,如下圖所示。

  

 

  查找。將二叉查找樹的查找算法一般化我們就能夠直接得到2-3樹的查找算法。要判斷一個鍵是否在樹中,我們先將它和根節點中的鍵比較。如果它和其中任意一個相等,查找命中;否則我們就根據比較的結果找到指向相應區間的鏈接,並在其指向的子樹中遞歸地繼續查找。如果這個是空連接,查找未命中。

  

 

  插入。要在2-3樹中插入一個新結點,我們可以和二叉查找樹一樣先進行一次未命中的查找,如果未命中的查找結束於一個2-結點,事情就好辦了:我們只要把這個2-結點替換為一個3-結點,將要插入的鍵保存在其中即可。如果未命中的查找結束於一個3-結點,事情就要麻煩一些。

    向2-結點中插入新鍵

    

 

    向一棵只含有一個3-結點的樹中插入新鍵

    

    

    向一個父節點為2-結點的3-結點中插入新鍵

    

 

    向一個父節點為3-結點的3-結點中插入新鍵

    

 

    插入結點到根節點的路徑上全都是3-結點

    

 

  

  和標准的二叉查找樹由上向下生長不同,2-3樹的生長是由下向上的:隨着結點的插入,臨時4-結點的中鍵不斷上浮,一旦根節點變成臨時4-結點,我們可以分解根結點完成樹的生長,使得樹高加1。
  下圖給出了我們的標准索引測試用例中產生的一系列2-3樹,以及一系列由同一組鍵按照升序一次插入到樹中時所產生的所有2-3樹。如果是在二叉查找樹中,按照升序插入10個鍵會得到高度為9的一棵最差查找樹,而使用2-3樹,樹的高度是2。

  

 

  在一棵大小為N的2-3樹中,查找和插入操作訪問的結點必然不超過lgN個
  盡管我們可以用不同的數據類型表示2-結點和3-結點並寫出變換所需的代碼,這樣我們需要維護兩種不同類型的結點,將被查找的鍵和結點中的每個鍵進行比較,將鏈接和其他信息從一種結點復制到另一種結點,將結點從一種數據類型轉換到另一種數據類型等等。實現這些不僅需要大量的代碼,而且它們所產生的額外開銷可能會使算法比標准的二叉查找樹更慢。幸運的是我們可以使用紅黑樹來解決這個問題,它以二叉樹的形式實現了2-3樹。

  

 

 

 

  紅黑樹

 

  定義。紅黑樹的一種定義是含有紅黑鏈接並滿足下列條件的二叉查找樹:

  紅鏈接均為左鏈接(左偏紅黑樹);
  沒有任何一個結點同時和兩條紅鏈接相連;
  該樹是完美黑色平衡的,即任意空鏈接到根節點的路徑上的黑鏈接數量相同。

  滿足這樣定義的紅黑樹和相應的2-3樹是一一對應的。
  如果我們將一棵紅黑樹中的紅鏈接畫平,那么所有的空鏈接到根節點的距離都將是相同的。如果我們將有紅鏈接相連的結點合並,得到的就是一棵2-3樹。
  相反,如果將一棵2-3樹中的3-結點畫作由紅色左鏈接相連的兩個2-結點,那么不會存在能夠和兩條紅鏈接相連的結點,且樹必然是完美黑色平衡的。
  紅黑樹既是二叉查找樹,也是2-3樹。因此如果我們能夠在保持一一對應關系的基礎上實現2-3樹的插入算法,那么我們就能夠將兩個算法的優點結合起來:二叉查找樹中簡潔高效的查找算法和2-3樹中高效的平衡插入算法。

  

 

  顏色表示。因為每個結點都只會有一條指向自己的鏈接(從它的父結點指向它),我們將鏈接的顏色保存在表示結點的Node數據類型的布爾變量color中。如果指向它的鏈接是紅色的,那么該變量為true,黑色則為false。我們約定空鏈接為黑色。

  

  

 

  旋轉。首先,假設我們有一條紅色的右鏈接需要被轉化為左鏈接,這個操作叫左旋轉。它只是將用兩個鍵中的較小者作為根節點變為將較大者作為根節點。
     實現將一個紅色左鏈接轉換為紅色右鏈接的右旋轉的代碼完全相同,只需要將left換成right即可。

    

 

  插入。在插入新的鍵時我們可以使用旋轉操作幫助我們保證紅黑樹和2-3樹之間的一一對應關系,因為旋轉操作可以保持紅黑樹的兩個重要性質:有序性完美平衡性。我們只需考慮如何使用旋轉操作來保持紅黑樹的另外兩個重要性質:不存在兩條連續的紅鏈接不存在紅色的右鏈接
  用和二叉查找樹相同的方式向一棵紅黑樹中插入一個新鍵會在樹的底部新增一個結點,但總是用紅鏈接將新結點和它的父結點相連
    向2-結點中插入新鍵

    

 

    向樹底部的2-結點插入新鍵

    

 

    向一個3-結點中插入新鍵。這種情況又可分為三種子情況:新鍵大於樹中的兩個鍵小於樹中的兩個鍵,或是在兩者之間

    

 

    顏色轉換。我們專門用一個方法flipColors()來轉換一個結點的兩個紅色子結點的顏色。顏色轉換會使根結點變成紅色,兩個子結點變成黑色。

    

 

    向樹底部的3-結點插入新鍵

    

 

  紅黑樹構造軌跡

  

 

 

  刪除。和插入操作一樣,我們也可以定義一系列局部變換來在刪除一個結點的同時保持樹的完美平衡性。這一過程比插入一個結點更加復雜,因為我們不僅要在(為了刪除一個結點而)構造臨時4-結點時沿着查找路徑向下進行變換,還要在分解遺留的臨時4-結點時沿着查找路徑向上進行變換(同插入操作)。

    刪除最小鍵。從樹底部的3-結點中刪除鍵是很簡單的,但2-結點則不然。從2-結點中刪除一個鍵會留下一個空結點,一般我們會將它替換為一個空鏈接,但這樣會破壞樹的完美平衡性。為了保證我們不會刪除一個2-結點,我們沿着左鏈接向下進行變換(最小鍵一定在左鏈接上),確保當前結點不是2-結點(即左鏈接上的結點可以是3-結點,也可以是臨時的4-結點),最后能夠得到一個含有最小鍵的3-結點或者臨時4-結點,然后我們就可以直接從中將其刪除,將3-結點變為2-結點,或者將臨時4-結點變為3-結點。然后我們再回頭向上分解所有臨時的4結點。

    

 

    刪除任意鍵。在查找路徑上進行和刪除最小鍵相同的變換同樣可以保證在查找過程中任意當前結點均不是2-結點。如果被查找的的鍵在樹的底部,我們可以直接刪除它。如果不在,我們需要將它和它的后繼結點交換。因為當前結點必然不是2-結點,問題已經轉化為在一棵根節點不是2-結點的子樹中刪除最小的鍵,我們可以在這棵子樹中使用上述的算法,刪除之后我們需要向上回溯並分解余下的臨時4-結點。

 

  紅黑樹最廣泛的應用是在C++的STL中,map和set都是用紅黑樹實現的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM