紅黑樹學習及Java實現

本文主要來自於美團技術,加了一些自己的理解和代碼註釋,並書寫自己在學習過程中的一些感想。

原代碼:源代碼歡迎start

參考文檔:https://tech.meituan.com/2016/12/02/redblack-tree.html

BST

二叉查找樹(Binary Search Tree,簡稱BST)是一棵二叉樹,它的左子節點的值比父節點的值要小,右節點的值要比父節點的值大。它的高度決定了它的查找效率。

在這裏插入圖片描述

在理想的情況下,二叉查找樹增刪查改的時間複雜度爲O(logN)(其中N爲節點數),最壞的情況下爲O(N)。當它的高度爲logN+1時,我們就說二叉查找樹是平衡的。

BST存在的問題

BST存在的主要問題是,數在插入的時候會導致樹傾斜,不同的插入順序會導致樹的高度不一樣,而樹的高度直接的影響了樹的查找效率。理想的高度是logN,最壞的情況是所有的節點都在一條斜線上,這樣的樹的高度爲N。

RBTree

基於BST存在的問題,一種新的樹——平衡二叉查找樹(Balanced BST)產生了。平衡樹在插入和刪除的時候,會通過旋轉操作將高度保持在logN。其中兩款具有代表性的平衡樹分別爲AVL樹和紅黑樹。AVL樹由於實現比較複雜,而且插入和刪除性能差,在實際環境下的應用不如紅黑樹。

紅黑樹(Red-Black Tree,以下簡稱RBTree)的實際應用非常廣泛,比如Linux內核中的完全公平調度器、高精度計時器、ext3文件系統等等,各種語言的函數庫如Java的TreeMap和TreeSet,C++ STL的map、multimap、multiset等。

RBTree也是函數式語言中最常用的持久數據結構之一,在計算幾何中也有重要作用。值得一提的是,Java 8中HashMap的實現也因爲用RBTree取代鏈表,性能有所提升。

RBTree的定義

RBTree的定義如下:

1. 任何一個節點都有顏色,黑色或者紅色
2. 根節點是黑色的
3. 父子節點之間不能出現兩個連續的紅節點,但是可以出現連續的黑色節點
4. 任何一個節點向下遍歷到其子孫的葉子節點,所經過的黑節點個數必須相等
5. 空節點被認爲是黑色的

RBTree在理論上還是一棵BST樹,但是它在對BST的插入和刪除操作時會維持樹的平衡,即保證樹的高度在[logN,logN+1](理論上,極端的情況下可以出現RBTree的高度達到2*logN,但實際上很難遇到)。這樣RBTree的查找時間複雜度始終保持在O(logN)從而接近於理想的BST。RBTree的刪除和插入操作的時間複雜度也是O(logN)。RBTree的查找操作就是BST的查找操作。

RBTree的旋轉操作

旋轉操作(Rotate)的目的是使節點顏色符合定義,讓RBTree的高度達到平衡。
Rotate分爲left-rotate(左旋)和right-rotate(右旋),區分左旋和右旋的方法是:待旋轉的節點從左邊上升到父節點就是右旋,待旋轉的節點從右邊上升到父節點就是左旋。即,左旋節點的父節點點從左邊下沉成爲子節點爲左旋,右旋節點的父節點從右邊下沉成爲子節點爲右旋

在這裏插入圖片描述

RBTree的查找操作

參考樹:二叉樹

RBTree的插入操作

RBTree的插入與BST的插入方式是一致的,只不過是在插入過後,可能會導致樹的不平衡,這時就需要對樹進行旋轉操作和顏色修復(在這裏簡稱插入修復),使得它符合RBTree的定義。

新插入的節點是紅色的,插入修復操作如果遇到父節點的顏色爲黑則修復操作結束。也就是說,只有在父節點爲紅色節點的時候是需要插入修復操作的。

插入修復操作分爲以下的三種情況,而且新插入的節點的父節點都是紅色的:

  1. 叔叔節點也爲紅色。
  2. 叔叔節點爲空,且祖父節點、父節點和新節點處於一條斜線上。
  3. 叔叔節點爲空,且祖父節點、父節點和新節點不處於一條斜線上。

插入操作-case 1

case 1的操作是將父節點和叔叔節點與祖父節點的顏色互換,這樣就符合了RBTRee的定義。即維持了高度的平衡,修復後顏色也符合RBTree定義的第三條。下圖中,操作完成後A節點變成了新的節點。如果A節點的父節點不是黑色的話(所以需要向上遍歷所有節點顏色),則繼續做修復操作。

在這裏插入圖片描述

插入操作-case 2

case 2的操作是將B節點進行右旋操作,並且和父節點A互換顏色。通過該修復操作RBTRee的高度和顏色都符合紅黑樹的定義。如果B和C節點都是右節點的話,只要將操作變成左旋就可以了。
在這裏插入圖片描述

插入操作-case 3

case 3的操作是將C節點進行左旋,這樣就從case 3轉換成case 2了,然後針對case 2進行操作處理就行了。case 2操作做了一個右旋操作和顏色互換來達到目的。如果樹的結構是下圖的鏡像結構,則只需要將對應的左旋變成右旋,右旋變成左旋即可。
在這裏插入圖片描述

插入操作的總結

插入後的修復操作是一個向root節點回溯的操作,一旦牽涉的節點都符合了紅黑樹的定義,修復操作結束。之所以會向上回溯是由於case 1操作會將父節點,叔叔節點和祖父節點進行換顏色,有可能會導致祖父節點不平衡(紅黑樹定義3)。這個時候需要對祖父節點爲起點進行調節(向上回溯)。

祖父節點調節後如果還是遇到它的祖父顏色問題,操作就會繼續向上回溯,直到root節點爲止,根據定義root節點永遠是黑色的。在向上的追溯的過程中,針對插入的3中情況進行調節。直到符合紅黑樹的定義爲止。直到牽涉的節點都符合了紅黑樹的定義,修復操作結束。

如果上面的3中情況如果對應的操作是在右子樹上,做對應的鏡像操作就是了。

刪除節點

刪除操作首先需要做的也是BST的刪除操作,刪除操作會刪除對應的節點,如果是葉子節點就直接刪除,如果是非葉子節點,會用對應的中序遍歷後繼節點來頂替要刪除節點的位置。刪除後就需要做刪除修復操作,使的樹符合紅黑樹的定義,符合定義的紅黑樹高度是平衡的。

刪除修復操作在遇到被刪除的節點是紅色節點或者到達root節點時,修復操作完畢。

刪除修復操作是針對刪除黑色節點(葉子節點爲紅色節點)纔有的,當黑色節點被刪除後會讓整個樹不符合RBTree的定義的第四條。需要做的處理是從兄弟節點上借調黑色的節點過來,如果兄弟節點沒有黑節點可以借調的話,就只能往上追溯,將每一級的黑節點數減去一個,使得整棵樹符合紅黑樹的定義。

刪除操作的總體思想是從兄弟節點借調黑色節點使樹保持局部的平衡,如果局部的平衡達到了,就看整體的樹是否是平衡的,如果不平衡就接着向上追溯調整

刪除修復操作分爲四種情況(刪除黑節點後):

  1. 待刪除的節點的兄弟節點是紅色的節點。
  2. 待刪除的節點的兄弟節點是黑色的節點,且兄弟節點的子節點都是黑色的。
  3. 待調整的節點的兄弟節點是黑色的節點,且兄弟節點的左子節點是紅色的,右節點是黑色的(兄弟節點在右邊),如果兄弟節點在左邊的話,就是兄弟節點的右子節點是紅色的,左節點是黑色的。
  4. 待調整的節點的兄弟節點是黑色的節點,且右子節點是是紅色的(兄弟節點在右邊),如果兄弟節點在左邊,則就是對應的就是左節點是紅色的。

未完待續。。。

代碼引申

CompareTo 和 equals 和 == 區別
  • CompareTo
    CompareTo 比較兩個類對象。就不能直接通過>,<進行比較,引用對象不是基本數據類型。兩個對象進行比較返回 “0”是 相等,正數是 大於,負數是小於

  • ==
    當複合數據類型用(==)進行比較,比較的是他們在內存中的存放地址。

  • equals
    當複合數據類型之間進行equals比較時,這個方法的初始行爲是比較對象在堆內存中的地址,但在一些諸如String,Integer,Date類中把Object中的這個方法覆蓋了,作用被覆蓋爲比較內容是否相同。

Java 中 比較兩個類對象。就不能直接通過>,<進行比較,引用對象不是基本數據類型

Java中很多類也都有CompareTo方法,甚至於排序算法的底層組成也是依賴於比較的,而這個比較就是依賴於各種數據類型的CompareTo或者Compare方法。Java中所有的compareTo方法都源於一個共同的接口,那就是Comparable。這個接口只有一個方法,那就是CompareTo。所有想要具有比較功能的類,都建議實現這個接口,而非是自己定義這個功能,這是面向對象的概念(將具有相同功能的事物抽象到一個共同的類或接口),並且爲了多態也建議通過實現接口來進行向上轉型,通過接口來操作具體實現,這也是面向接口編程要求我們做的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章