跳躍表(skiplist )是一種隨機化的數(shù)據(jù),由 William Pugh 在論文《Skip lists: a probabilistic alternative to balanced trees》 中提出,跳躍表以有序的方式在層次化的鏈表中保存元素,效率和平衡樹媲美 ——查找、刪除、添加等操作都可以在對數(shù)期望時間下完成,并且比起平衡樹來說,跳躍表的實現(xiàn)要簡單直觀得多。
以下是個典型的跳躍表例子:
從圖中可以看到,跳躍表主要由以下部分構(gòu)成:
NULL
組成,表示跳躍表的末尾。因為跳躍表的定義可以在任何一本算法或數(shù)據(jù)結(jié)構(gòu)的書中找到,所以本章不介紹跳躍表的具體實現(xiàn)方式或者具體的算法,而只介紹跳躍表在 Redis 的應(yīng)用、核心數(shù)據(jù)結(jié)構(gòu)和 API 。
為了滿足自身的功能需要,Redis 基于 William Pugh 論文中描述的跳躍表進(jìn)行了以下修改:
score
值:多個不同的 member
的 score
值可以相同。score
值,還要檢查 member
:當(dāng) score
值可以重復(fù)時,單靠 score
值無法判斷一個元素的身份,所以需要連 member
域都一并檢查才行。這個修改版的跳躍表由 redis.h/zskiplist
結(jié)構(gòu)定義:
typedef struct zskiplist {
// 頭節(jié)點,尾節(jié)點
struct zskiplistNode *header, *tail;
// 節(jié)點數(shù)量
unsigned long length;
// 目前表內(nèi)節(jié)點的最大層數(shù)
int level;
} zskiplist;
跳躍表的節(jié)點由 redis.h/zskiplistNode
定義:
typedef struct zskiplistNode {
// member 對象
robj *obj;
// 分值
double score;
// 后退指針
struct zskiplistNode *backward;
// 層
struct zskiplistLevel {
// 前進(jìn)指針
struct zskiplistNode *forward;
// 這個層跨越的節(jié)點數(shù)量
unsigned int span;
} level[];
} zskiplistNode;
以下是操作這兩個數(shù)據(jù)結(jié)構(gòu)的 API ,API 的用途與相應(yīng)的算法復(fù)雜度:
函數(shù) | 作用 | 復(fù)雜度 |
---|---|---|
zslCreateNode |
創(chuàng)建并返回一個新的跳躍表節(jié)點 | 最壞 (O(1)) |
zslFreeNode |
釋放給定的跳躍表節(jié)點 | 最壞 (O(1)) |
zslCreate |
創(chuàng)建并初始化一個新的跳躍表 | 最壞 (O(1)) |
zslFree |
釋放給定的跳躍表 | 最壞 (O(N)) |
zslInsert |
將一個包含給定 score 和 member 的新節(jié)點添加到跳躍表中 |
最壞 (O(N)) 平均 (O(\log N)) |
zslDeleteNode |
刪除給定的跳躍表節(jié)點 | 最壞 (O(N)) |
zslDelete |
刪除匹配給定 member 和 score 的元素 |
最壞 (O(N)) 平均 (O(\log N)) |
zslFirstInRange |
找到跳躍表中第一個符合給定范圍的元素 | 最壞 (O(N)) 平均 (O(\log N)) |
zslLastInRange |
找到跳躍表中最后一個符合給定范圍的元素 | 最壞 (O(N)) 平均 (O(\log N)) |
zslDeleteRangeByScore |
刪除 score 值在給定范圍內(nèi)的所有節(jié)點 |
最壞 (O(N^2)) |
zslDeleteRangeByRank |
刪除給定排序范圍內(nèi)的所有節(jié)點 | 最壞 (O(N^2)) |
zslGetRank |
返回目標(biāo)元素在有序集中的排位 | 最壞 (O(N)) 平均 (O(\log N)) |
zslGetElementByRank |
根據(jù)給定排位,返回該排位上的元素節(jié)點 | 最壞 (O(N)) 平均 (O(\log N)) |
和字典、鏈表或者字符串這幾種在 Redis 中大量使用的數(shù)據(jù)結(jié)構(gòu)不同,跳躍表在 Redis 的唯一作用,就是實現(xiàn)有序集數(shù)據(jù)類型。
跳躍表將指向有序集的 score
值和 member
域的指針作為元素,并以 score
值為索引,對有序集元素進(jìn)行排序。
舉個例子,以下代碼創(chuàng)建了一個帶有 3 個元素的有序集:
redis> ZADD s 6 x 10 y 15 z
(integer) 3
redis> ZRANGE s 0 -1 WITHSCORES
1) "x"
2) "6"
3) "y"
4) "10"
5) "z"
6) "15"
在底層實現(xiàn)中,Redis 為 x
、 y
和 z
三個 member
分別創(chuàng)建了三個字符串,值分別為 double
類型的 6
、 10
和 15
,然后用跳躍表將這些指針有序地保存起來,形成這樣一個跳躍表:
zskipNode\n(head) |<3> |<2> |<1> |score\n NULL |robj\n NULL", fillcolor = "#F2F2F2"]; six [label = "zskipNode |<3> |<2> |<1> |score\n 6 |robj\n x", fillcolor = "#95BBE3"]; ten [label = "zskipNode | <1> |score\n 10 |robj\n y", fillcolor = "#95BBE3"]; fiften [label = "zskipNode |<3> |<2> |<1> |score\n 15 |robj\n z", fillcolor = "#95BBE3"]; skiplist:3 -> six:3; skiplist:2 -> six:2; skiplist:1 -> six:1; six:1 -> ten:1; six:2 -> fiften:2; six:3 -> fiften:3; ten:1 -> fiften:1; null_1 [label = "NULL", shape=plaintext]; null_2 [label = "NULL", shape=plaintext]; null_3 [label = "NULL", shape=plaintext]; fiften:1 -> null_1; fiften:2 -> null_2; fiften:3 -> null_3;}" />
為了方便展示,在圖片中我們直接將 member
和 score
值包含在表節(jié)點中,但是在實際的定義中,因為跳躍表要和另一個實現(xiàn)有序集的結(jié)構(gòu)(字典)分享 member
和 score
值,所以跳躍表只保存指向 member
和 score
的指針。更詳細(xì)的信息,請參考《有序集》章節(jié)。
score
值可重復(fù)。score
和 memeber
。
更多建議: