我们来聊聊「跳表(Skip List)」,这是一个既经典又优雅的数据结构,尤其在 Redis 中非常重要,比如 ZSet
(有序集合)底层就用到了跳表。
🌟 跳表(Skip List)简介
跳表是一种有序链表的升级版,通过引入多级“索引层”来加快查找效率。
✅ 主要特点:
特性 | 说明 |
---|---|
有序结构 | 跳表维护的是一个按 key 排序的链表 |
多级索引 | 类似高速公路入口,有“快捷路径” |
查找时间复杂度 | O(log n),跟平衡树差不多 |
实现简单 | 不像红黑树那样复杂 |
支持范围查找 | 很适合 Redis 中的范围查询场景 |
📐 跳表结构长啥样?
假设我们有以下数据:[1, 3, 5, 7, 9]
我们构建跳表的过程如下(用 →
表示链表):
Level 2: 1 → → → 9
Level 1: 1 → 5 → → 9
Level 0: 1 → 3 → 5 → 7 → 9
你可以把它想象成“高架桥+地面道路”的组合:
- 第 0 层是原始链表,所有节点都在这里
- 每上一层都是稀疏的抽样索引,跳过一些中间节点,加快查找速度
- 查找从最上层开始,逐层向下,“跳跃式前进”
🔍 查找过程举例:查找 7
- 从最上层的 1 开始 → 跳到 9(9 > 7,不行)
- 回退到下一层 → 1 → 5 → 9(9 > 7,继续往下)
- 到最底层 → 5 → 7 ✅ 找到!
→ 相比普通链表要顺着一格一格走,跳表“跳着走”,很快!
📈 跳表的时间复杂度
操作 | 时间复杂度 |
---|---|
查找 | O(log n) |
插入 | O(log n) |
删除 | O(log n) |
空间复杂度 | O(n) |
相比红黑树、AVL 树,它的代码实现更简单、更易于维护。
💡 跳表的应用场景
场景 | 用途 |
---|---|
Redis ZSet | 支持快速插入、范围查询(如 zrangebyscore) |
LSM-Tree(如 RocksDB) | 早期实现用过跳表 |
时间序列数据 | 有序插入、范围检索 |
分布式系统 | 一致性哈希环也可用跳表改进检索速度 |
🔧 跳表 vs 红黑树
项目 | 跳表 | 红黑树 |
---|---|---|
插入复杂度 | O(log n) | O(log n),但旋转逻辑复杂 |
实现难度 | 简单 | 复杂(需维持平衡) |
查询性能 | 相似 | 相似 |
空间开销 | 稍高(多指针) | 较少 |
🧪 小技巧:跳表的层数怎么选?
跳表是随机化结构,每次插入一个节点时,以一定概率(如 1/2)决定它是否晋升到上一层。
最常见的方式是:
while (random() < p && level < MAX_LEVEL) {level++;
}
所以跳表是一种「概率平衡」的数据结构。
📦 总结
优点 | 缺点 |
---|---|
实现简单、性能稳定 | 占用内存较多 |
支持快速范围查找 | 查询时间有轻微波动 |
非常适合缓存、有序集合 | 不如 B 树适合磁盘存储 |
在 Redis 中,跳表(Skip List) 主要用于 Sorted Set(有序集合,zset) 的底层实现。
✅ 使用跳表的场景:zset
(有序集合)
Redis 的 zset
类型是一个既可以快速查找元素、又能按照分数排序的数据结构。为了实现这个特性,它使用了 两种结构 来共同实现:
结构 | 作用 |
---|---|
哈希表(dict) | 用于通过成员名快速定位其分数,O(1) 时间复杂度 |
跳表(skiplist) | 用于按照分数进行有序排列,支持范围查找、按排名查找等,平均 O(log n) 复杂度 |
🧠 为什么 Redis 用跳表而不是红黑树?
虽然红黑树、AVL 树等自平衡二叉树也能满足排序要求,但 Redis 选择跳表是因为:
- 实现简单、易于维护
- 插入和删除操作更平滑,少量随机性避免最坏情况
- 天然支持范围查询和按 rank 查找
- 跳表是线性结构,遍历更快,利于内存预取(cache friendly)
🧪 举个例子:
127.0.0.1:6379> ZADD myzset 1 a 2 b 3 c
127.0.0.1:6379> ZRANGE myzset 0 -1 WITHSCORES
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"
在内部:
- Redis 用 dict 维护
a → 1
,b → 2
,c → 3
- 同时用跳表按分数 1 → 2 → 3 排列节点,方便范围查询,比如
ZRANGEBYSCORE
🔧 Redis 跳表结构核心代码(在源码中)
源码路径:/src/t_zset.c
关键结构:zskiplist
、zskiplistNode
typedef struct zskiplistNode {robj *obj; // 成员对象double score; // 分数struct zskiplistNode *backward;struct zskiplistLevel {struct zskiplistNode *forward;unsigned int span;} level[];
} zskiplistNode;
✅ 总结
Redis 中只有
zset
使用跳表,结合 hash dict 实现高效的查找和排序。