在跳表中查询任意数据的时平均时间复杂度就是 O(logn) 。
跳表查询的空间复杂度分析在这里的话,我们假设它的长度为 n,然后按照之前的例子,每两个节点抽一个做成一个索引的话,那么它的一级索引为二分之 n 对吧 。最后如下:
- 原始链表大小为 n,每 2 个结点抽 1 个,每层索引的结点数: n2,n4,n8...,8,4,2
- 原始链表大小为 n,每 3 个结点抽 1 个,每层索引的结点数: n3,n9,n27...,9,3,1
- 空间复杂度是 O(n)

文章插图
从图中可以看到,跳跃表主要由以下部分构成:
- 表头(head):负责维护跳跃表的节点指针 。
- 跳跃表节点:保存着元素值,以及多个层 。
- 层:保存着指向其他元素的指针 。高层的指针越过的元素数量大于等于低层的指针,为了提高查找的效率,程序总是从高层先开始访问,然后随着元素值范围的缩小,慢慢降低层次 。
- 表尾:全部由 NULL 组成,表示跳跃表的末尾 。

文章插图
上图展示了一个跳跃表示例,位于图片最左边的是 zskiplist 结构,该结构包含以下属性:
- header :指向跳跃表的表头节点 。
- tail :指向跳跃表的表尾节点 。
- level :记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内) 。
- length :记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内) 。
- 层(level):节点中用 L1 、 L2 、 L3 等字样标记节点的各个层, L1 代表第一层, L2 代表第二层,以此类推 。每个层都带有两个属性:前进指针和跨度 。前进指针用于访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离 。在上面的图片中,连线上带有数字的箭头就代表前进指针,而那个数字就是跨度 。当程序从表头向表尾进行遍历时,访问会沿着层的前进指针进行 。
- 后退(backward)指针:节点中用 BW 字样标记节点的后退指针,它指向位于当前节点的前一个节点 。后退指针在程序从表尾向表头遍历时使用 。
- 分值(score):各个节点中的 1.0 、 2.0 和 3.0 是节点所保存的分值 。在跳跃表中,节点按各自所保存的分值从小到大排列 。
- 成员对象(obj):各个节点中的 o1 、 o2 和 o3 是节点所保存的成员对象 。
跳跃表节点跳跃表节点的实现由 redis.h/zskiplistNode 结构定义:
typedef struct zskiplistNode { // 后退指针 struct zskiplistNode *backward; // 分值 double score; // 成员对象 robj *obj; // 层 struct zskiplistLevel { // 前进指针 struct zskiplistNode *forward; // 跨度 unsigned int span; } level[];} zskiplistNode;层跳跃表节点的 level 数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度,一般来说,层的数量越多,访问其他节点的速度就越快 。每次创建一个新跳跃表节点的时候,程序都根据幂次定律 (power law,越大的数出现的概率越小) 随机生成一个介于 1 和 32 之间的值作为 level 数组的大小,这个大小就是层的“高度” 。
推荐阅读
- 八张图了解Redis和MySQL数据一致性问题
- Nginx可以做什么?看完这篇你就懂了
- 基于Redis实现的分布式锁知识点总结
- 基于SSM实现的个人网盘系统源码
- 从Linux源码看Socket的listen及连接队列
- 大佬把Spring框架总结的「无比详细」,看完还说不懂别学了
- Spring Cloud微服务分布式物联网平台前后端分离源码
- 一口气看完45个寄存器,CPU核心技术大揭秘
- Python爬虫遇到验证码的几种处理方式,文章末尾有源码
- 运动|“长期化妆”和“长期素颜”,5年后皮肤有什么区别?看完你就懂
