跳跃表

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

跳跃表是在有序链表的基础上实现的

在有序链表中,查找元素时,只能逐一查找,时间复杂度为 O(N),操作十分缓慢。为了优化,可以考虑在链表其中的元素上建索引的方式,就得到了链表。

跳跃表在节点查询的时间复杂度平均为 O(logN),最坏情况下 O(N),还可以通过顺序性操作来批量处理节点。

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

Redis 使用跳跃表作为有序集合键的底层实现之一,如果一个有序列表包含的元素数量比较多,又或者有序集合中元素的成员是比较长的字符串时,Redis 就会使用跳跃表来作为有序集合键的底层实现。

Redis 只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构。

跳跃表的实现

跳跃表节点

typedef struct zskiplistNode {
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned int span;
    } level[];
    struct zskiplistNode *backward;
    double score;
    robj *obj;
} zskiplistNode;
  • level : 包含多个元素,每个元素都包含一个指向其它节点的指针,程序可以通过这些层来加快访问其它节点的速度,一般来说,层的数量越多,访问其它节点的速度就越快;

    每次创建一个新跳跃表节点的时候,程序都根据幂次定律随机生成一个介于 1 和 32 之间的值作为 level 数组的大小,这个大小就是层的“高度”

    • forward : 每个层都有一个指向表尾方向的前进指针,用于从表头向表尾方向访问节点。
    • span : 代表层的跨度,用于记录两个节点之间的距离。两个节点之间的跨度越大, 它们相距就越远;指向 null 的所有前进指针的跨度都为 0,因为它们没有连向任何节点。
  • backward : 节点的后退指针用于从表尾想表头方向访问节点。跟可以一次跳过多个节点的前进指针不同,因为每个结点只有一个后退指针,所以每次只能后退至前一个节点。
  • score : 跳跃表的所有节点都按分值从小到大来排序。
  • obj : 是一个指针,指向一个字符串对象,而字符串对象则保存着一个 SDS 值。

在同一个跳跃表中,各个节点保存的成员对象必须是唯一的,但是多个节点保存的分值却是可以相同的。分值相同的节点将按照成员对象在字典序中的大小来进行排序,成员对象较小的节点会排在前面(靠近表头的方向),而成员对象较大的节点则会排在后面(靠近表尾的方向)。

跳跃表

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;
  • header : 指向跳跃表的表头指针
  • tail : 指向跳跃表的表尾指针
  • length : 记录节点的数量
  • level : 记录跳跃表中层数最大的节点的层数

代码笔耕
0 声望1 粉丝