Redis数据结构——skiplist(跳跃表)

跳跃表在Redis中主要用于有序集合键的实现,其他地方没怎么用到,但是这种数据结构在面试的时候经常会问到,因为它作为一种查找时间复杂度为O(logN)的特殊的链表,效率堪比红黑树或平衡树,而实现难度却远小于它们。下面分3个模块讲解Redis的跳跃表实现:

 

一、跳跃表的应用场景

在Redis中,当有序集合包含的元素数量较多,或者有序集合中元素的成员是比较长的字符串时,就会使用跳跃表做有序集合键的底层实现。其中,跟有序集合有关的命令主要有ZADD、ZRANGE等

 

二、跳跃表的数据结构

首先介绍下跳跃表的节点定义:

type struct zskiplistNode {
    struct zskiplistLevel {
        struct zskiplistNode *forward;    //前进指针
        unsigned int span;    //跨度
    }level[];    //level数组,数组索引表示层数,最多32层(索引值为0~31)
    struct zskiplistNode *backward;    //后退指针
    double score;    //分数
    robj *obj;    //成员对象
}zskiplistNode;

看起来很复杂是吧,没关系,下面对结构体的所有成员变量一个个介绍:

1. level[]

每个跳跃表节点都包含一个level数组,这个数组包含1~32个元素,具体是多少呢,由幂次定律决定,假设为1的概率为x,那么为2的概率就为(1-x)*x,以此类推,为n的概率为(1-x)^{^{n-1}}*x,这个数组元素越多,说明层数越多,访问其他节点的速度就会越快。

level数组的每个元素都是一个zskiplistLevel结构体变量,这个变量包含2个元素forward和span,其中forward是一个指向下一个跳跃表节点的前进指针,span表示与下一个跳跃表节点的跨度,不理解的话可以看下面的例子说明。

2. backward

后退指针主要用于指向上一个跳跃表节点,形成逆向链表(个人觉得作用不大)

3. score

有序集合的分值,排序时用到,决定了跳跃表节点的位置

4. obj

obj是一个指向实际成员对象的指针

 

下面介绍完整的跳跃表定义:

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;    //分别指向跳跃表头和表尾
    unsigned long length;    //跳跃表中节点数量
    int level;    //跳跃表中除跳跃表头节点之外层数最大的节点的层数,最大为32
}zskiplist;

其中需要注意的一点是,header指向的跳跃表头节点固定有32层(即该节点对应的level数组有32个元素),且一开始的时候每一层指向的下一个跳跃表节点都为NULL,直到有节点插入到跳跃表中去。

 

三、跳跃表的插入过程(重点):

1. 一开始跳跃表是空的,如下图:

Redis数据结构——skiplist(跳跃表)_第1张图片

2. 先插入元素A,对应的score为5,随机生成的层数为2

Redis数据结构——skiplist(跳跃表)_第2张图片

3. 再插入元素B,对应的score为3,随机生成的层数为1

Redis数据结构——skiplist(跳跃表)_第3张图片

4. 最后再插入元素C,对应的score为7,随机生成的层数为1

Redis数据结构——skiplist(跳跃表)_第4张图片

5. 此时如果要查找分值为7的元素C,先从最高层的链表往右查,发现最上面的链表的第一个元素(不考虑header指向的节点)的分值为5<7,就往右前进,此时最上面的链表已经没有下一个节点了,所以就降一层再往右查,进而查到元素C的存在,整个过程其实直接跳过了对元素A对应节点的访问,从而提升了查询效率。

 

总结:

其实跳跃表就是由level条前进链表+1条后退链表构成的,后退链表的存在主要用于从后往前遍历所有跳跃表节点,而多条前进链表的存在则是为了提升查询效率。

你可能感兴趣的:(Redis)