redis底层数据结构之跳表(skiplist)

跳表(跳跃表, skiplist)

跳跃表(skiplist)是用于有序元素序列快速搜索查找的数据结构,跳表是一个随机化的数据结构,实质是一种可以进行二分查找的、具有层次结构的有序链表

跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找,平均期望的查找、插入、删除时间复杂度都是O(logn),同时支持范围查询

 

具有如下性质:
1) 每个节点由多层组成,排列顺序为由高层到底层

2) 每一层都是一个有序链表

3) 最底层的链表包含了所有的元素

4) 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现

5) 每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点

6) 分数(score)允许重复,即key是允许重复的

 

1 zskiplist结构

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

其中:

header: 表头节点,没有存储实际的数据,而是一个空的,初始化层级数为ZSKIPLIST_MAXLEVEL值(默认32)的节点

tail: 表尾节点

length: 节点数量

level: 最大层级,表头节点层数不计

 

2 zskiplistNode结构

typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;

其中:

ele:节点对象,即节点数据

score:当前节点值对应的分值,用于排序,按分值从小到大来排序,各个节点对象必须是唯一的, 但是多个节点保存的分值却可以是相同的, 分值相同的节点按照成员对象值从小到大排序

backward:当前节点的上一个节点

level:表示层数

  forward:同一层的下一个节点

  span:跨度,当前节点到forward指向的节点跨越了多少个节点

 

3 skiplist示意图

 

 

 

4 skiplist插入节点

步骤如下:

1) 找到每一层新节点要插入的位置(update):从高层到低层遍历skiplist,找到每一层小于新节点score的最大的节点,如果节点score相等,则比较ele

2) 随机分配一个层数(level),如果层数比skiplist的层数大,增加skiplist的层数并修改新加层数的span

3) 插入新节点(x):新节点每一层(x->level[i]) 的forward修改为每一层插入位置(update[i]->level[i]) 的forward,每一层插入插入位置的forward修改为新节点(顺序不能反),更新新节点(x),插入位置节点(update[i]->level[i]) 的每一层的span

4) 修改更高的level的span

5) 修改新节点的backward(x->backward)

6) 修改新节点最低层后一个节点(x->level[0].forward)的backward:如果新节点的forward不为null,则新节点后一个节点的backward为新节点;否则新节点是skiplist的尾结点

7) 修改skiplist的节点数量

 

src/t_zset.c文件对应的源码如下:

/* Insert a new node in the skiplist. Assumes the element does not already
 * exist (up to the caller to enforce that). The skiplist takes ownership
 * of the passed SDS string 'ele'. */
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned long rank[ZSKIPLIST_MAXLEVEL];
    int i, level;

    serverAssert(!isnan(score));
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        /* store rank that is crossed to reach the insert position */
        rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
        while (x->level[i].forward &&
                (x->level[i].forward->score < score ||
                    (x->level[i].forward->score == score &&
                    sdscmp(x->level[i].forward->ele,ele) < 0)))
        {
            rank[i] += x->level[i].span;
            x = x->level[i].forward;
        }
        update[i] = x;
    }
    /* we assume the element is not already inside, since we allow duplicated
     * scores, reinserting the same element should never happen since the
     * caller of zslInsert() should test in the hash table if the element is
     * already inside or not. */
    level = zslRandomLevel();
    if (level > zsl->level) {
        for (i = zsl->level; i < level; i++) {
            rank[i] = 0;
            update[i] = zsl->header;
            update[i]->level[i].span = zsl->length;
        }
        zsl->level = level;
    }
    x = zslCreateNode(level,score,ele);
    for (i = 0; i < level; i++) {
        x->level[i].forward = update[i]->level[i].forward;
        update[i]->level[i].forward = x;

        /* update span covered by update[i] as x is inserted here */
        x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
        update[i]->level[i].span = (rank[0] - rank[i]) + 1;
    }

    /* increment span for untouched levels */
    for (i = level; i < zsl->level; i++) {
        update[i]->level[i].span++;
    }

    x->backward = (update[0] == zsl->header) ? NULL : update[0];
    if (x->level[0].forward)
        x->level[0].forward->backward = x;
    else
        zsl->tail = x;
    zsl->length++;
    return x;
}

 

 

 

5 skiplist删除节点

步骤如下:

1) 找到每一层要删除节点(x) 的前一个节点(update):从高层到低层遍历skiplist,每一层都找到小于新节点score的最大的节点,如果节点score相等,则比较ele

2) 删除节点,更新每一层要删除节点前一个节点(udpate[i]) 的span和forward

3) 修改要删除节点后一个节点最低层([x->level[0].forward->backward]) 的backward:如果删除节点最低层的forward不为null,则要删除节点后一个节点最低层的backward为要删除节点的backward,否则要删除节点后一个节点是skiplist的尾结点

4) 修改skiplist的层数level

5) 修改skiplist的节点个数

 

src/t_zset.c文件对应的源码如下:

/* Internal function used by zslDelete, zslDeleteRangeByScore and
 * zslDeleteRangeByRank. */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
    int i;
    for (i = 0; i < zsl->level; i++) {
        if (update[i]->level[i].forward == x) {
            update[i]->level[i].span += x->level[i].span - 1;
            update[i]->level[i].forward = x->level[i].forward;
        } else {
            update[i]->level[i].span -= 1;
        }
    }
    if (x->level[0].forward) {
        x->level[0].forward->backward = x->backward;
    } else {
        zsl->tail = x->backward;
    }
    while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
        zsl->level--;
    zsl->length--;
}

 

posted @ 2022-12-17 18:03  junffzhou  阅读(313)  评论(0编辑  收藏  举报