程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 紅黑樹算法的層層剖析與逐步實現

紅黑樹算法的層層剖析與逐步實現

編輯:關於C語言

本文主要參考:算法導論第二版
本文主要代碼:參考算法導論。
本文圖片來源:個人手工畫成、算法導論原書。
推薦閱讀:Leo J. Guibas 和 Robert Sedgewick 於1978年寫的關於紅黑樹的一篇論文。

引言: 

昨天下午畫紅黑樹畫了好幾個鐘頭,總共10頁紙。
特此,再深入剖析紅黑樹的算法實現,教你如何徹底實現紅黑樹算法。

經過我上一篇博文,“教你透徹了解紅黑樹”後,相信大家對紅黑樹已經有了一定的了解。
個人覺得,這個紅黑樹,還是比較容易懂的。
不論是插入、還是刪除,不論是左旋還是右旋,最終的目的只有一個:
即保持紅黑樹的5個性質,不得違背。

再次,重述下紅黑樹的五個性質:
一般的,紅黑樹,滿足一下性質,即只有滿足一下性質的樹,我們才稱之為紅黑樹:
1)每個結點要麼是紅的,要麼是黑的。
2)根結點是黑的。
3)每個葉結點,即空結點(NIL)是黑的。
4)如果一個結點是紅的,那麼它的倆個兒子都是黑的。
5)對每個結點,從該結點到其子孫結點的所有路徑上包含相同數目的黑結點。


抓住了紅黑樹的那5個性質,事情就好辦多了。
如,
1.紅黑紅黑,要麼是紅,要麼是黑;
2.根結點是黑;
3.每個葉結點是黑;
4.一個紅結點,它的倆個兒子必然都是黑的;
5.每一條路徑上,黑結點的數目等同。
   五條性質,合起來,來句順口溜就是:(1)紅黑 (2)黑 (3)黑 (4&5)紅->黑 黑。

 

本文所有的文字,都是參照我昨下午畫的十張紙(即我拍的照片)與算法導論來寫的。

希望,你依照此文一點一點的往下看,看懂此文後,你對紅黑樹的算法了解程度,一定大增不少。

 

ok,現在咱們來具體深入剖析紅黑樹的算法,並教你逐步實現此算法。

此教程分為10個部分,每一個部分作為一個小節。且各小節與我給的十張照片一一對應。

 

一、左旋與右旋

     先明確一點:為什麼要左旋?

因為紅黑樹插入或刪除結點後,樹的結構發生了變化,從而可能會破壞紅黑樹的性質。

為了維持插入、或刪除結點後的樹,仍然是一顆紅黑樹,所以有必要對樹的結構做部分調整,從而恢復紅黑樹的原本性質。

而為了恢復紅黑性質而作的動作包括:

結點顏色的改變(重新著色),和結點的調整。

這部分結點調整工作,改變指針結構,即是通過左旋或右旋而達到目的。

從而使插入、或刪除結點的樹重新成為一顆新的紅黑樹。

 

ok,請看下圖:

\

如上圖所示,‘找茬’

如果你看懂了上述倆幅圖有什麼區別時,你就知道什麼是“左旋”,“右旋”。

 

在此,著重分析左旋算法:

左旋,如圖所示(左->右),以x->y之間的鏈為“支軸”進行,

使y成為該新子樹的根,x成為y的左孩子,而y的左孩子則成為x的右孩子。

算法很簡單,還有注意一點,各個結點從左往右,不論是左旋前還是左旋後,結點大小都是從小到大。

 

左旋代碼實現,分三步(注意我給的注釋):

The pseudocode for LEFT-ROTATE assumes that right[x] ≠ nil[T] and that the roots parent is nil[T].

LEFT-ROTATE(T, x)
 1  y ← right[x]            ▹ Set y.
 2  right[x] ← left[y]                   //開始變化,y的左孩子成為x的右孩子

 3  if left[y]  !=nil[T]

 4  then p[left[y]] <- x                

 5  p[y] <- p[x]                       //y成為x的父母
 6  if p[x] = nil[T]

 7     then root[T] <- y

 8     else if x = left[p[x]]
 9             then left[p[x]] ← y
10             else right[p[x]] ← y
11  left[y] ← x             //x成為y的左孩子(一月三日修正)

12  p[x] ← y
//注,此段左旋代碼,原書第一版英文版與第二版中文版,有所出入。

//個人覺得,第二版更精准。所以,此段代碼以第二版中文版為准。

 

左旋、右旋都是對稱的,且都是在O(1)時間內完成。因為旋轉時只有指針被改變,而結點中的所有域都保持不變。

最後,貼出昨下午關於此右旋算法所畫的圖:

左旋(第2張圖):

\

//此圖有點bug。第4行的注釋移到第11行。如上述代碼所示。(一月三日修正)

 

二、左旋的一個實例

不做過多介紹,看下副圖,一目了然。

LEFT-ROTATE(T, x)的操作過程(第3張圖):

\ 

--------------------- 

提醒,看下文之前,請首先務必明確,區別以下倆種操作:

1.紅黑樹插入、刪除結點的操作

         //如插入中,紅黑樹插入結點操作:RB-INSERT(T, z)。

2.紅黑樹已經插入、刪除結點之後,

為了保持紅黑樹原有的紅黑性質而做的恢復與保持紅黑性質的操作。

        //如插入中,為了恢復和保持原有紅黑性質,所做的工作:RB-INSERT-FIXUP(T, z)。

ok,請繼續。

 

三、紅黑樹的插入算法實現

RB-INSERT(T, z)   //注意我給的注釋...
 1  y ← nil[T]                 // y 始終指向 x 的父結點。
 2  x ← root[T]              // x 指向當前樹的根結點,
 3  while x ≠ nil[T]
 4      do y ← x
 5         if key[z] < key[x]           //向左,向右..
 6            then x ← left[x]
 7            else x ← right[x]         // 為了找到合適的插入點,x 探路跟蹤路徑,直到x成為NIL 為止。
 8  p[z] ← y         // y置為 插入結點z 的父結點。
 9  if y = nil[T]
10     then root[T] ← z
11     else if key[z] < key[y]
12             then left[y] ← z
13             else right[y] ← z     //此 8-13行,置z 相關的指針。
14  left[z] ← nil[T]
15  right[z] ← nil[T]            //設為空,
16  color[z] ← RED             //將新插入的結點z作為紅色
17  RB-INSERT-FIXUP(T, z)   //因為將z著為紅色,可能會違反某一紅黑性質,

                                            //所以需要調用RB-INSERT-FIXUP(T, z)來保持紅黑性質。

17 行的RB-INSERT-FIXUP(T, z) ,在下文會得到著重而具體的分析。

還記得,我開頭說的那句話麼,

是的,時刻

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved