資料結構(一)--- 跳躍表

MouseDong發表於2019-08-01

1、簡述

跳躍表(skiplist)是一種優秀的資料查詢結構,查詢原理類似於2分查詢,平均的查詢時間複雜度為O(logN);

其底層基於連結串列實現,但區別在於含有多層,每個節點的每層都有指向表尾方向最近一個節點的指標;

各種語言對跳躍表的實現可能不同,但主要原理是相同的,所以這裡只是所以下原理,

    • 圖中是含有四層結構的跳躍表,bw是指向前一個節點的指標,每個節點只有一個,刪除時方便。
    • 每個節點都含有一個或多個指向表尾最近一個節點的指標。
    • 最底層(L1層)包含所有的節點。
    • 圖中bw筆者參考了Redis實現的跳躍表結構,自主新增上的,便於更好的理解。

 

2、查詢

 跳躍表的優秀表現在於查詢功能,以上圖中查詢值為90的節點為例,如果在連結串列中繼續要進行順序查詢,需要進行9步才能查詢到,而在上圖的跳躍表中則需要6步就能完成,具體步驟如下:

(1)由最高層L4層開始查詢,L4層當前節點值10,小於90,則取當前點的下一個節點120,大於90,這時降層到L3層查詢;即查詢值處於當前節點的值和當前節點下一節點的值之間時,降層查詢。

(2)當前節點為10,L3層,取其下一節點40比較,小於90,進行下一步。

(3)節點40,取其下一節點80比較,小於90,進行下一步。

(4)節點80,取其下一節點120比較,大於90,此時將80設定為當前節點,並在當前節點上降層,進行下一步。

(5)當前節點80,L2層,取下一節點100比較,大於90,當前節點不變,直接降層,進行下一步。

(6)當前節點80,L1層,取下一節點90比較,等於90,結束,返回。

一共經過6步,雖然說只節省了3步,但這是在我們查詢的是90節點,如果我們查詢的是120節點,那麼只需要1步便可以查詢到了,所以其平均時間複雜度為O(logN);跳躍表在節點數量少的情況下,效能的提升不明顯,當節點在1w以上時,效能提升將會非常明顯。

 

3、插入

跳躍表節點的插入過程就是上圖中跳躍表結構的構造過程,下面將實現一個只有頭結點(不含值)的跳躍表插入節點的過程。

1、插入節點10

(1)節點10插入空跳躍表,插入L1層,如圖.

(2)此時拋硬幣決定,是否加層;拋硬幣正面,節點10增加L2層。

(3)拋硬幣決定,是否繼續加層,拋硬幣正面,節點10繼續增加L3層。

(4)繼續拋硬幣決定,是否繼續加層,拋硬幣反面,結束加層,節點10插入完成;最終結構如下圖(3)。

 

2、插入節點30

   (1)節點30插入跳躍表L1層,L1層存在節點10,與其比較,大於10,放入節點10後邊,更新節點10L1層指標,指向新節點30,節點30的bw指標,指向節點10,如下圖(1)。

(2)此時拋硬幣,正面,節點30增加L2層,並更改節點10的L2層的指標,指向新節點30的L2層。

(3)再次拋硬幣,反面,結束加層,節點30插入完成;最終結構如下圖(2)。 

 

 

以此類推,新增節點過程就是判定層次是否增加的過程;但有幾點需要說明一下:

    • 圖中所有空層的指標都指向的是NULL ,為使圖簡潔這點忽略。
    • 頭結點的層數,每種實現的語言不一樣,初始化的層數不一樣,Redis的實現為32層,也就是最高就是32層,不可能再多,其他語言根據設定不同,層數不同;初始化的頭結點層指標都指向null。
    • 關於新節點插入哪層的問題,在網上普遍有兩種方法,筆者採用的是拋硬幣法;兩種方法思路如下:
      1. 每層拋硬幣法:新節點都插入最底層L1,然後採用拋硬幣的方法決定是否在存在上一層;正面存在,進行增加層;反面不存在,則插入結束。
      2. 隨機值決定層數:新節點插入之前,先用隨機值決定一共有幾層,然後根據跳躍表高層含有的節點其低層一定含有的特點,插入資料;隨機演算法思路是先給定一個概率p,產生一個0到1之間的隨機數,如果隨機數小於p,則將層加1,直到產生的隨機數大於概率p則結束,根據給出的結論,當概率為1/2或者是1/4的時候,整體的效能會比較好(其實當p為1/2的時候,也就是拋硬幣的方法)。

4、刪除

 節點的刪除其實就是一個查詢節點,然後修改指標,刪除節點的過程;如我們需要刪除圖中的節點60,步驟如下:

(1)查詢節點60,一共需要4步(參考第2節查詢)。

(2)修改L2層的前一節點的指標指向後一節點,修改後一節點的bw指標指向前一節點;修改L1層的前一節點指標執行後一節點。

(3)刪除節點60,結束。

 

5、總結

    • 在資料規模比較小時,跳躍表形成後可能不是理想的跳躍表結構,但是當資料量增大,結構越接近理想的跳躍表結構。
    • bw指標不是所有的語言的實現都存在,這個是參考了Redis的跳躍表實現,筆者自己加上去的。
    • 跳躍表不同於樹結構,如紅黑樹等,它不需要花費過多的精力進行平衡演算法,這也是跳躍表的效能優越的一個方面。
    • 跳躍表結構是拿空間換時間的一種結構,儘管空間佔用不是很大。
    • 查詢、刪除,平均時間複雜度都是O(logn),而插入的平均時間複雜度也是O(logn) 因為涉及到增層操作,所以這裡需要注意不是O(n).

 

部落格講解文字較多,推薦一篇以漫畫方式講解跳躍表的博文,更容易理解:《漫畫:什麼是跳躍表

此部落格為筆者參考網路上各類文章總結性書寫,原創手打,如有錯誤歡迎指正。

相關文章