跳表

# 数据结构

每个元素记录

// src/server.h

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned int span;
    } level[];
} zskiplistNode;

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

其中最重要的就是backward和level。

  • backward可以理解为每一个node的上一个。
  • level可以理解为当前层级以及所有下层。span表示当前层中当前node和下一个node forward的距离。

例如一个跳表:

level1: 1 2 3 4 5;
level2: 1 3 5
level3: 1 5

我们对于3这个node,他属于level2和level1,那么其level[]就包含两层,其中
level2: 3 -> span=1 forward=5
level1: 3 -> span=0 forward=4 -> span=0 forward=5

# 查找

跳表的高度是lg(n),查找复杂度为lg(n)

# 增加和删除

增加的时候对于每个增加的元素,计算其level的概率。我们假设整个跳表按照2的次方来计算索引,那么可以认为每一个level 概率p为1/2。

那么理解为每一个新增的node依次向上进行level p概率“晋升”。level 1 p=1,level 2 p=1/2,level 3 p=1/2 * 1/2=1/4 等。

redis中的实现如下:

其中random()是随机数,而ZSKIPLIST_P就是晋升概率,可以自己设置。如果晋升概率小,则整体空间利用率会比较高,但是查找的性能会下降。

// src/t_zset.c

/* Returns a random level for the new skiplist node we are going to create.
 * The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL
 * (both inclusive), with a powerlaw-alike distribution where higher
 * levels are less likely to be returned. */
int zslRandomLevel(void) {
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

跳表的高度是lg(n),因此复杂度都是lg(n)。