为什么使用跳跃表?

跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。

在大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树要简单,所以有不少程序都使用跳跃表来代替平衡树。

跳跃表是什么?

跳跃表是一种有序数据结构,他通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问的目的。

跳跃表在哪使用?

跳跃表是有序集合的底层实现之一。

一、跳跃表结构与实现

整体结构图:

redis在哪里用到跳跃表 redis跳跃表实现_skiplist

跳跃表节点redis.h/zskiplistNode

typedef struct zskiplistNode{
    //层
    struct zskiplistLevel{
        //前进指针
        struct zskiplistNode *forward;
        //跨度
        unsigned int span;
    }level[];
    //后退指针
    struct zskiplistNode *backward;
    //分值(用于排序)
    double score;
    //成员对象
    robj *obj;
}zskiplistNode;

层(level):上图中的L1、L2、L3等字样,L1表示第一层、L2表示第二层,以此类推。每层有两个属性:前进指针和跨度。前进指针用于访问位于标为方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离。上图中带有数字的箭头就代表前进指针,所带的数字表示跨度。当程序从表头向表尾进行遍历时,访问会沿着层的前进指针进行。

后退(backward)指针:上图中BW字样表示后退指针。它指向位于当前节点的前一个节点。后退指针在程序从表尾到表头遍历时使用。

分值(score):上图中的1.0、2.0、3.0即对应节点所保存的分值。

成员对象(obj):上图中的o1、o2、o3即对应节点所保存的对象。

注:表头节点和其他节点构造是一样的,但其中的后退指针、分值、成员对象不会被用到。只会用层。

redis.h/zskiplist

typedef struct zskiplist{
    //表头节点和表尾节点
    struct zskiplistNode *header,*tail;
    //表中节点数量
    unsigned long length;
    //表中层数最大的节点的层数(表头节点的层高并不计算在内)
    int level;
}zskiplist;

二、

层的作用:跳跃表节点的level数组可以包含多个元素,每个元素包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度,一般来说,层的数量越多访问其他节点的速度就越快。

层的高度:

每次创建一个新跳跃表节点的时候,程序都根据幂次定律(power law,越大的数出现的概率越小)随机生成一个介于1-32之间的只作为level数组的大小,这个大小就是层的高度。

分数排序规则:

1、跳跃表中所有节点都按分值从小到大排序

2、分值相同的节点将按成员对象在字典序中的大小来排序,成员对象较小的节点会排在前面(靠近表头的方向),而成员对象较大的节点则会排在后面(靠近表尾的方向),简言之:分值相同按字典序中表头到表尾的顺序排序。

成员对象唯一性:

在同一个跳跃表中,各个节点保存的成员对象必须是唯一的。

zskiplist作用:

获取表头节点、表尾节点、节点数量、level最大层高的复杂度为O(1)