【Redis基本数据结构】跳跃表实现

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

跳跃表支持平均$O(log)$、最坏$O(N)$ 复杂度的节点查找. 大部分情况下,跳跃表的效率可以和平衡树想媲美,并且跳跃表的实现比平衡树更为简单.
<!– more –>
Redis 使用跳跃表作为有序集合键的底层实现之一, 如果一个有序集合包含的元素数量较多,或者有序集合中元素的成员是比较长的字符串, Redis 会使用跳跃表来作为有序集合的底层实现.

和链表、字典等数据结构被广泛应用在 Redis 中不同, Redis 只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构, 除此之外,跳跃表在 Redis 中没有其他用途.

跳跃表的实现

Redis 的跳跃表是有 redis.h/zskiplistNoderedis.h/zskiplist 两个结构定义

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

结构体各成员说明如下:

  • 跳跃表的 level 数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序可以通过这些指针加快访问速度,一般来说,层的数量越多,访问其他节点的速度越快.
    
    每次创建一个新跳跃表节点时,程序会根据幂次定律(越大的数出现的概率越小)随机生成一个介于1 和 32 之间的值作为 level 数组的大小,这个大小就是层的高度
  • 前进指针

    每个层都有一个指向表尾方向的指针.用于从表头向表尾方向访问节点.
  • 跨度

    层的跨度用于记录两个节点之间的距离. 两个节点之间的跨度越大,它们距离越远;指向 NULL 的节点的跨度为0.
  • 后退指针

    后退指针用于从表尾向表头访问节点,跟可以一次跳过多个节点的前进指针不同,每个节点只有一个后退指针.
  • 分值和成员

    节点的分支是一个 double 类型的浮点数,跳跃表中的所有节点都按分值从小到大排序.

    节点的成员对象是一个指针,指向一个字符串对象,而字符串对象保存着一个 SDS 值.

各节点保存的成员对象必须是唯一的,但是多个节点保存的分值可以是相同的: 分值相同的节点按照成员对象在字典序中的大小来排序,成员对象较小的节点会排在前面(靠近表头的方向).

下图显示一个简单的跳跃表布局:

《【Redis基本数据结构】跳跃表实现》

图的左边为 zskiplist 结构,用来管理跳跃表节点.
zskiplist 结构定义如下:

typedef struct zskiplist {
    struct zskiplistNode *header, *tail; //表头和表尾指针
    unsigned long length;   //节点的数量
    int level;      //层数最大的节点的层数
} zskiplist;

表头节点并没有算到 节点数量里面,表头节点和其他节点的构造是一样的:有前进指针、后退指针、分值和成员对象,不过这些属性都不会用到,所以图中省略这些部分,只显示表头节点的各层.

根据跳跃表的结构,程序可以在$O(1)$复杂度内返回表的长度,$O(1)$复杂度内定位表头节点和表尾节点.

我的博客: http://ygmyth.github.io

    原文作者:ygmyth
    原文地址: https://segmentfault.com/a/1190000004864003
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞