【Redis基本數據結構】跳躍表實現

跳躍表( skiplist) 是一種有序的數據結構, 它經過在每一個節點中維持多個指向其餘節點的指針,從而達到快速訪問節點的目的.git

跳躍表支持平均$O(log)$、最壞$O(N)$ 複雜度的節點查找. 大部分狀況下,跳躍表的效率能夠和平衡樹想媲美,而且跳躍表的實現比平衡樹更爲簡單.
<!-- more -->
Redis 使用跳躍表做爲有序集合鍵的底層實現之一, 若是一個有序集合包含的元素數量較多,或者有序集合中元素的成員是比較長的字符串, Redis 會使用跳躍表來做爲有序集合的底層實現.github

和鏈表、字典等數據結構被普遍應用在 Redis 中不一樣, Redis 只在兩個地方用到了跳躍表,一個是實現有序集合鍵,另外一個是在集羣節點中用做內部數據結構, 除此以外,跳躍表在 Redis 中沒有其餘用途.redis

跳躍表的實現

Redis 的跳躍表是有 redis.h/zskiplistNoderedis.h/zskiplist 兩個結構定義數組

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

結構體各成員說明以下:數據結構

  • 佈局

    跳躍表的 level 數組能夠包含多個元素,每一個元素都包含一個指向其餘節點的指針,程序能夠經過這些指針加快訪問速度,通常來講,層的數量越多,訪問其餘節點的速度越快.
    
    每次建立一個新跳躍表節點時,程序會根據冪次定律(越大的數出現的機率越小)隨機生成一個介於1 和 32 之間的值做爲 level 數組的大小,這個大小就是層的高度
  • 前進指針spa

    每一個層都有一個指向表尾方向的指針.用於從表頭向表尾方向訪問節點.
  • 跨度指針

    層的跨度用於記錄兩個節點之間的距離. 兩個節點之間的跨度越大,它們距離越遠;指向 NULL 的節點的跨度爲0.
  • 後退指針code

    後退指針用於從表尾向表頭訪問節點,跟能夠一次跳過多個節點的前進指針不一樣,每一個節點只有一個後退指針.
  • 分值和成員對象

    節點的分支是一個 double 類型的浮點數,跳躍表中的全部節點都按分值從小到大排序.

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

各節點保存的成員對象必須是惟一的,可是多個節點保存的分值能夠是相同的: 分值相同的節點按照成員對象在字典序中的大小來排序,成員對象較小的節點會排在前面(靠近表頭的方向).

下圖顯示一個簡單的跳躍表佈局:

簡單的跳躍表

圖的左邊爲 zskiplist 結構,用來管理跳躍表節點.
zskiplist 結構定義以下:

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

表頭節點並無算到 節點數量裏面,表頭節點和其餘節點的構造是同樣的:有前進指針、後退指針、分值和成員對象,不過這些屬性都不會用到,因此圖中省略這些部分,只顯示錶頭節點的各層.

根據跳躍表的結構,程序能夠在$O(1)$複雜度內返回表的長度,$O(1)$複雜度內定位表頭節點和表尾節點.

個人博客: http://ygmyth.github.io

相關文章
相關標籤/搜索