跳躍表
跳躍表簡介
跳躍表(skiplist)是一種有序數據結構, 它通過在每個節點中維持多個指向其他節點的指針, 從而達到快速訪問節點的目的。
跳躍表支持平均 O(log N) 最壞 O(N) 複雜度的節點查找, 還可以通過順序性操作來批量處理節點。
在大部分情況下, 跳躍表的效率可以和平衡樹相媲美, 並且因爲跳躍表的實現比平衡樹要來得更爲簡單, 所以有不少程序都使用跳躍表來代替平衡樹。
Redis 使用跳躍表作爲有序集合鍵的底層實現之一: 如果一個有序集合包含的元素數量比較多, 又或者有序集合中元素的成員(member)是比較長的字符串時, Redis 就會使用跳躍表來作爲有序集合鍵的底層實現。
跳躍表的實現
跳躍表節點的實現由 redis.h/zskiplistNode 結構定義:
typedef struct zskiplistNode {
// 後退指針
struct zskiplistNode *backward;
// 分值
double score;
// 成員對象
robj *obj;
// 層
struct zskiplistLevel {
// 前進指針
struct zskiplistNode *forward;
// 跨度
unsigned int span;
} level[];
} zskiplistNode;
zskiplist 結構的定義如下:
typedef struct zskiplist {
// 表頭節點和表尾節點
struct zskiplistNode *header, *tail;
// 表中節點的數量
unsigned long length;
// 表中層數最大的節點的層數
int level;
} zskiplist;
初看上去, 很容易以爲跨度和遍歷操作有關, 但實際上並不是這樣 —— 遍歷操作只使用前進指針就可以完成了, 跨度實際上是用來計算排位(rank)的: 在查找某個節點的過程中, 將沿途訪問過的所有層的跨度累計起來, 得到的結果就是目標節點在跳躍表中的排位。
注意表頭節點和其他節點的構造是一樣的: 表頭節點也有後退指針、分值和成員對象, 不過表頭節點的這些屬性都不會被用到, 所以圖中省略了這些部分, 只顯示了表頭節點的各個層。
分值和成員
- 節點的分值(score 屬性)是一個 double 類型的浮點數, 跳躍表中的所有節點都按分值從小到大來排序。
- 節點的成員對象(obj 屬性)是一個指針, 它指向一個字符串對象, 而字符串對象則保存着一個 SDS 值。
在同一個跳躍表中, 各個節點保存的成員對象必須是唯一的, 但是多個節點保存的分值卻可以是相同的: 分值相同的節點將按照成員對象在字典序中的大小來進行排序, 成員對象較小的節點會排在前面(靠近表頭的方向), 而成員對象較大的節點則會排在後面(靠近表尾的方向)。
參考資料