跳躍表( skiplist) 是一種有序的數據結構, 它經過在每一個節點中維持多個指向其餘節點的指針,從而達到快速訪問節點的目的.git
跳躍表支持平均$O(log)$、最壞$O(N)$ 複雜度的節點查找. 大部分狀況下,跳躍表的效率能夠和平衡樹想媲美,而且跳躍表的實現比平衡樹更爲簡單.
<!-- more -->
Redis 使用跳躍表做爲有序集合鍵的底層實現之一, 若是一個有序集合包含的元素數量較多,或者有序集合中元素的成員是比較長的字符串, Redis 會使用跳躍表來做爲有序集合的底層實現.github
和鏈表、字典等數據結構被普遍應用在 Redis 中不一樣, Redis 只在兩個地方用到了跳躍表,一個是實現有序集合鍵,另外一個是在集羣節點中用做內部數據結構, 除此以外,跳躍表在 Redis 中沒有其餘用途.redis
Redis 的跳躍表是有 redis.h/zskiplistNode
和 redis.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