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

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

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

和链表、字典等数据结构被普遍应用在 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 数组的大小,这个大小就是层的高度
  • 前进指针spa

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

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

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

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

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

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

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

简单的跳跃表

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

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

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

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

个人博客: http://ygmyth.github.io

相关文章
相关标签/搜索