跳跃表( skiplist) 是一种有序的数据结构, 它经过在每一个节点中维持多个指向其余节点的指针,从而达到快速访问节点的目的.git
跳跃表支持平均$O(log)$、最坏$O(N)$ 复杂度的节点查找. 大部分状况下,跳跃表的效率能够和平衡树想媲美,而且跳跃表的实现比平衡树更为简单.
<!-- more -->
Redis 使用跳跃表做为有序集合键的底层实现之一, 若是一个有序集合包含的元素数量较多,或者有序集合中元素的成员是比较长的字符串, Redis 会使用跳跃表来做为有序集合的底层实现.github
和链表、字典等数据结构被普遍应用在 Redis 中不一样, Redis 只在两个地方用到了跳跃表,一个是实现有序集合键,另外一个是在集群节点中用做内部数据结构, 除此以外,跳跃表在 Redis 中没有其余用途.redis
Redis 的跳跃表是有 redis.h/zskiplistNode
和 redis.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