dbeaver导出表结构和数据_[redis]zset数据结构-跳跃表

zset数据结构

zset有序集合,底层采用跳跃表数据结构来实现。

我们先了解一下“跳跃表”这种数据结构。查找事件复杂度较好的数据结构有。

  1. 平衡树BST:查找时间复杂度O(
    ),但是插入和删除后满足平衡需要多次旋转移动节点,最多需要
    次。
  2. 红黑树:为了解决“平衡树BST”多次旋转的问题引入的,通过降低平衡的要求。
  3. 跳跃表:虽然“红黑树”降低旋转次数,但是操作还是较为复杂,故引入跳跃表。通过用空间换时间的方式来解决操作复杂的问题。

e72f3559b73885ee902620a3300fc5c7.png

下面时redis源码zskiplistNode表示一个节点

// redis.h
typedef struct zskiplistNode {
    robj *obj;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        // 跨度
        unsigned int span;
    } level[];
} zskiplistNode;
  • obj:用于存放该节点的具体数据。
  • score:权重,以此数据值来进行排序。
  • backward:向后指针,用于level为0时的指向后一个数据节点。所以最底层是一个双向链表数据结构。
  • level[]:用于表示一个节点数据的层。
这里有个C语言知识点"[]",是C99标准引入的可变长数组。
  • forward:为指向下一个数据节点。
  • span:表示向前指针跳过了多少个节点,用于排名查找,如查找score第10个的数据,可通过span快速查找O(
    ),不然需要遍历时间复杂度O(n)。

跳跃表创建:

// t_zset.c
zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);

ZSKIPLIST_MAXLEVEL默认32层,即头结点是一个32层的结点,在上图中可以查看到。

插入一个元素结点

zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;

    redisAssert(!isnan(score));

    // T_wrost = O(N^2), T_avg = O(N log N)
    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];

        // T_wrost = O(N^2), T_avg = O(N log N)
        while (x->level[i].forward &&
            (x->level[i].forward->score < score ||
                (x->level[i].forward->score == score &&
                compareStringObjects(x->level[i].forward->obj,obj) < 0))) {
            rank[i] += x->level[i].span;
            x = x->level[i].forward;
        }
        update[i] = x;
    }

    /* we assume the key is not already inside, since we allow duplicated
     * scores, and the re-insertion of score and redis object should never
     * happen since the caller of zslInsert() should test in the hash table
     * if the element is already inside or not. 
     *
     */
    // T = O(N)
    level = zslRandomLevel();
    if (level > zsl->level) {
        // T = O(1)
        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,obj);
    // T = O(1)
    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 */
    // T = O(1)
    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;
}
  • zslRandomLevel():采用随机的方式得出要插入的数据的层数。ZSKIPLIST_P的默认0.25即1/4,此处算法理解:(random()&0xFFFF)是均匀分布在0~0xFFFF上,ZSKIPLIST_P限制了范围,故此处表示1/4的概率level会增加,即level在高层的概率为1/4。
// t_zset.c
int zslRandomLevel(void) {
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
有人建议在处while中判断添加level的条件判断“level<ZSKIPLIST_MAXLEVEL”,经过计算后发现没必要:while条件成立的概率1/4如果要达到默认的ZSKIPLIST_MAXLEVEL 32层的概率:
很小很小。
  • x=zslCreateNode(level,score,obj):创建一个节点,该节点的level层,score排序权重,obj数据内容。
  • 创建出节点元素“x”后,采用for循环按层遍历,修改指针指向。

总结

  1. 跳跃表在实现较为容易简单的情况下代替了“红黑树”。
  2. 保证了较高的时间复杂度O(logn),通过牺牲一定的空间,空间复杂度O(n)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值