跳躍表

原始跳躍表結構理解

對於下面這個鏈表,我們需要查找元素9。一共遍歷8個節點才能找到元素9

由於元素是有序的,我們可以通過增加一些路徑來加快查找速度。例如:

通過這種方法,我們只需要遍歷5次就可以找到元素9 了(紅色線路爲查找路徑)。

如果我們再加一層,只需要查找4次就能找到了。

 

 

 

 

 

這種方法,對於具有n個元素的鏈表,我們可以採取(logn+1)層指針路徑的形式,就可以實現在O(logn)的時間複雜度內,查找到某個目標元素了,這種數據結構,我們稱之爲跳躍表。

 

跳躍表的有關性質

(1). 跳躍表的每一層都是一條有序的鏈表.

(2). 跳躍表的查找次數近似於層數,時間複雜度爲O(logn),插入、刪除也爲 O(logn)。

(3). 最底層的鏈表包含所有元素。

(4). 跳躍表是一種隨機化的數據結構(通過拋硬幣來決定層數)。

(5). 跳躍表的空間複雜度爲 O(n)。

 

 

跳躍表節點zskiplistNode

從下面的代碼中我們可以看到跳躍表的node結構是分爲四種,一個是成員對象,一個是分值,一個是後退指針,另一個是跳躍表層級,其中層級是一個結構體,包含了指向跳躍表node的前進指針和跨度。所以我們關心的對象值是保存在obj中的。

/* ZSETs use a specialized version of Skiplists */ 
typedef struct zskiplistNode { 
//成員對象 
robj *obj; 
//分值 
double score; 
//後退指針 
struct zskiplistNode *backward; 
//層 
struct zskiplistLevel { 
//前進指針 
    struct zskiplistNode *forward;
//跨度
    unsigned int span;
 } level[]; 
} zskiplistNode;

 

分值和成員

 節點的分值(score屬性)是一個double類型的浮點數,跳躍表中的所有節點都按照分值從小到大來排序。

  節點的成員對象是一個指針,他指向一個字符串對象,而字符串對象則保存着一個SDS值。

  在同一個跳躍表中,各個節點保存的成員對象必須是唯一的,但是多個節點保存的分值卻可以是相同的:分值相同的節點按照成員對象在字典中的大小來進行排序,成員對象較小的節點會排在前面(靠近表頭方向),而成員對象較大的節點則會排在後面(靠近表尾的方向)。

   舉個例子,在下圖所示的跳躍表中,三個跳躍表節點都保存了相同的分值10086.0,但保存成員對象o1的節點卻排在保存成員對象o2和o3的節點之前,由順序可知,三個對象在字典中的排序哦o1<=o2<=o3。

跳躍表節點的level數組可以包含多個元素,每個元素都包含一個指向其他節點的指針,程序可以通過這些層來加快訪問其他節點的速度,一般來說,層數越多,訪問其他節點的速度就越快。

每次創建一個新的跳躍表節點的時候,程序都根據冪次定律(power law,越大的數出現的概率越小)隨機生成一個介於1和32之間的值作爲level數組的大小,這個大小就是層的“高度”。下圖就是帶有不同高度的節點。前進指針

每一層都有一個指向表尾方向的前進指針,用於從表頭向表尾方向訪問節點。下圖用虛線表示了程序從表頭向表尾方向,遍歷跳躍表中所有節點的路徑:迭代程序首先訪問跳躍表的一個節點(表頭),然後從第一個節點中的L4的前進指針移動到表中的第二個節點的L4。

  1. 在第二個節點時,程序沿着第二層的前進指針移動到表中第三個節點。
  2. 在第三個節點時,程序同樣沿着第二層的前進指針移動到表中的第四個節點。
  3. 當程序再次沿着第四個節點的前進指針移動時,它碰到了一個null,程序知道這時已經到達了跳躍表的表尾,於是結束這次遍歷。

跨度

層的跨度用於記錄兩個節點之間的距離:

  1. 兩個節點之間的跨度越大,他們相距得距離就越遠。
  2. 指向null的所有前進指針的跨度都爲0,因爲他們沒有連向任何節點。

 

初看上去,很容易以爲跨度和遍歷操作有關,但實際上並不是這樣的,遍歷操作只是用前進指針就可以完成,跨度實際上是用來計算排位的(rank);在查找某個節點的過程中,將沿途訪問過的所有層的跨度累計起來,得到的結果就是目標節點在跳躍表中的排位。

舉個例子,下圖用虛線標記了在跳躍表中查找分值爲3.0、成員對象爲o3的節點時,沿途經歷的層:查找的過程只經歷了一個層,並且跨度爲3,所以目標節點在跳躍表中的排位爲3.

 

 

後退指針

節點的後退指針用於從表尾向表頭方向訪問節點:跟可以一次跳過多個節點的前進指針不同,因爲每個節點只有一個後退指針,所以每次只能後退至前一個節點。

下圖用虛線表示瞭如果從表尾向表頭遍歷跳躍表中的所有節點。

 

跳躍表zskiplist

僅靠多個跳躍表節點就可以組成一個跳躍表,如下圖。

 

但通過使用一個zskiplist結構來持有這些節點,程序可以更方便地對整個跳躍表進行處理,比如快速訪問跳躍表的表頭節點和表尾節點,或者快速地獲取跳躍表節點的數量等信息。

typedef struct zskiplist { 
    //表頭節點和表尾節點 
    struct zskiplistNode *header, *tail; 
    //表中節點的的數量 
    unsigned long length; 
    //表中層數最大的節點層數 
    int level; 
} zskiplist;

 

header和tail指針分別指向跳躍表的表頭和表尾節點,通過這兩個指針,程序定位表頭及誒點和表尾節點的複雜度爲O(1)。

 通過使用length屬性來記錄節點的數量,程序可以在O(1)複雜度內返回跳躍表的長度。

  level屬性則用於在O(1)複雜度內獲取跳躍表中層高最大的那個節點的層數量,注意表頭節點的層高並不計算在內。

跳躍表API

 

參考文章:

https://www.cnblogs.com/qixinbo/p/9682721.html

http://www.sohu.com/a/293236470_298038

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