【數據結構】紅黑樹與跳表-(SortSet)-(TreeMap)-(TreeSet)


SortSet

  有序的Set,其實在Java中TreeSet是SortSet的唯一實現類,內部通過TreeMap實現的;而TreeMap是通過紅黑樹實現的;而在Redis中是通過跳表實現的;

SkipList

  跳表,思想類似平衡二叉樹,但又不一樣;下面摘了一個介紹:

  skiplist數據結構簡介(摘自:https://www.cnblogs.com/Elliott-Su-Faith-change-our-life/p/7545940.html 

  skiplist本質上也是一種查找結構,用於解決算法中的查找問題(Searching),即根據給定的key,快速查到它所在的位置(或者對應的value)。

  我們在《Redis內部數據結構詳解》系列的第一篇中介紹dict的時候,曾經討論過:一般查找問題的解法分為兩個大類:一個是基於各種平衡樹,一個是基於哈希表。但skiplist卻比較特殊,它沒法歸屬到這兩大類里面。

  這種數據結構是由William Pugh發明的,最早出現於他在1990年發表的論文《Skip Lists: A Probabilistic Alternative to Balanced Trees》。對細節感興趣的同學可以下載論文原文來閱讀。

skiplist,顧名思義,首先它是一個list。實際上,它是在有序鏈表的基礎上發展起來的。

  我們先來看一個有序鏈表,如下圖(最左側的灰色節點表示一個空的頭結點):

  在這樣一個鏈表中,如果我們要查找某個數據,那么需要從頭開始逐個進行比較,直到找到包含數據的那個節點,或者找到第一個比給定數據大的節點為止(沒找到)。也就是說,時間復雜度為O(n)。同樣,當我們要插入新數據的時候,也要經歷同樣的查找過程,從而確定插入位置。

  假如我們每相鄰兩個節點增加一個指針,讓指針指向下下個節點,如下圖:

 

  這樣所有新增加的指針連成了一個新的鏈表,但它包含的節點個數只有原來的一半(上圖中是7, 19, 26)。現在當我們想查找數據的時候,可以先沿着這個新鏈表進行查找。當碰到比待查數據大的節點時,再回到原來的鏈表中進行查找。比如,我們想查找23,查找的路徑是沿着下圖中標紅的指針所指向的方向進行的:

  • 23首先和7比較,再和19比較,比它們都大,繼續向后比較。

  • 但23和26比較的時候,比26要小,因此回到下面的鏈表(原鏈表),與22比較。

  • 23比22要大,沿下面的指針繼續向后和26比較。23比26小,說明待查數據23在原鏈表中不存在,而且它的插入位置應該在22和26之間。

  在這個查找過程中,由於新增加的指針,我們不再需要與鏈表中每個節點逐個進行比較了。需要比較的節點數大概只有原來的一半。

  利用同樣的方式,我們可以在上層新產生的鏈表上,繼續為每相鄰的兩個節點增加一個指針,從而產生第三層鏈表。如下圖:

 

  在這個新的三層鏈表結構上,如果我們還是查找23,那么沿着最上層鏈表首先要比較的是19,發現23比19大,接下來我們就知道只需要到19的后面去繼續查找,從而一下子跳過了19前面的所有節點。可以想象,當鏈表足夠長的時候,這種多層鏈表的查找方式能讓我們跳過很多下層節點,大大加快查找的速度。

  skiplist正是受這種多層鏈表的想法的啟發而設計出來的。實際上,按照上面生成鏈表的方式,上面每一層鏈表的節點個數,是下面一層的節點個數的一半,這樣查找過程就非常類似於一個二分查找,使得查找的時間復雜度可以降低到O(log n)。但是,這種方法在插入數據的時候有很大的問題。新插入一個節點之后,就會打亂上下相鄰兩層鏈表上節點個數嚴格的2:1的對應關系。如果要維持這種對應關系,就必須把新插入的節點后面的所有節點(也包括新插入的節點)重新進行調整,這會讓時間復雜度重新蛻化成O(n)。刪除數據也有同樣的問題。

  skiplist為了避免這一問題,它不要求上下相鄰兩層鏈表之間的節點個數有嚴格的對應關系,而是為每個節點隨機出一個層數(level)。比如,一個節點隨機出的層數是3,那么就把它鏈入到第1層到第3層這三層鏈表中。為了表達清楚,下圖展示了如何通過一步步的插入操作從而形成一個skiplist的過程(點擊看大圖):

  從上面skiplist的創建和插入過程可以看出,每一個節點的層數(level)是隨機出來的,而且新插入一個節點不會影響其它節點的層數。因此,插入操作只需要修改插入節點前后的指針,而不需要對很多節點都進行調整。這就降低了插入操作的復雜度。實際上,這是skiplist的一個很重要的特性,這讓它在插入性能上明顯優於平衡樹的方案。這在后面我們還會提到。

  根據上圖中的skiplist結構,我們很容易理解這種數據結構的名字的由來。skiplist,翻譯成中文,可以翻譯成“跳表”或“跳躍表”,指的就是除了最下面第1層鏈表之外,它會產生若干層稀疏的鏈表,這些鏈表里面的指針故意跳過了一些節點(而且越高層的鏈表跳過的節點越多)。這就使得我們在查找數據的時候能夠先在高層的鏈表中進行查找,然后逐層降低,最終降到第1層鏈表來精確地確定數據位置。在這個過程中,我們跳過了一些節點,從而也就加快了查找速度。

  剛剛創建的這個skiplist總共包含4層鏈表,現在假設我們在它里面依然查找23,下圖給出了查找路徑:

  需要注意的是,前面演示的各個節點的插入過程,實際上在插入之前也要先經歷一個類似的查找過程,在確定插入位置后,再完成插入操作。

  至此,skiplist的查找和插入操作,我們已經很清楚了。而刪除操作與插入操作類似,我們也很容易想象出來。這些操作我們也應該能很容易地用代碼實現出來。

  當然,實際應用中的skiplist每個節點應該包含key和value兩部分。前面的描述中我們沒有具體區分key和value,但實際上列表中是按照key進行排序的,查找過程也是根據key在比較。

 

紅黑樹:

  這個介紹就多了,總結一下,一個自平衡的二叉查找樹。

 


免責聲明!

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



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