AVL树、红黑树、b+树、跳表
AVL树、红黑树、b+树
比较:
AVL树:要求任何节点的左右子树高度差不超过1。这非常严格,稍有倾斜就要立刻调整。avl树太严格了,插入或删除旋转调整太多,会慢,但是查询会快,因为高度底点。
红黑树:要求没那么苛刻,允许一侧比另一侧最多长2层(通过黑色节点数量来近似平衡)。红黑树层高,所以查慢一点,但是插入或删除旋转调整少,所以插入删除会快。红黑树层高,但是节约内存,维护简单点。内存里层高没事,主要是要快,要占用内存少,所以hashmap这种内存里的用红黑树
b+树:层少,就可以减少查找磁盘次数,所以MySQL的索引用b+树。但是维护麻烦,一个节点的大小固定,填不满就浪费内存了
简单比较:
AVL树:层少,高度差严格,插入或删除旋转调整太多,但是查询会快。
红黑:层高,节约空间,维护简单
b+树:层少,占空间,维护麻烦
一句话选型建议
- 内存、读多写少、对查询延迟要求极致 → AVL
- 内存、读写均衡、追求综合性能 → 红黑树(Linux内核、C++ STL map、Java HashMap)
- 磁盘、大量数据、需要范围查询 → B+树(MySQL InnoDB)
- 磁盘、点查为主、写少 → LSM树(RocksDB,但那是另一回事了)
核心差异总结
| 特性 | AVL树 | 红黑树 | B+树 |
|---|---|---|---|
| 平衡标准 | 高度差 ≤1(严格) | 黑色节点数平衡(宽松) | 多路搜索树 |
| 高度 | 最低 | 较高 | 最低(多叉) |
| 查询速度 | 最快 | 较快 | 快(但常数大) |
| 插入/删除 | 慢(旋转多) | 快(旋转少) | 中等(分裂/合并) |
| 适用场景 | 内存中读多写少 | 内存中读写均衡 | 磁盘存储(数据库) |
| 内存占用 | 较高(存平衡因子) | 较低(只需1位颜色) | 较高(节点大,有浪费) |
redis的zset为什么选择跳表
Redis 的 ZSET 理论上也可以用 B+树或 AVL 树,但它最终选择了跳表。
Redis ZSET 的需求:
├── 内存中运行(不涉及磁盘)
├── 需要按分数排序(有序)
├── 需要范围查询(ZRANGE、ZREVRANGE 高频)
├── 需要单点查询 ZSCORE(O(1) 由 dict 解决)
├── 需要单点插入/删除
└── 代码简洁、稳定、Bug 少
跳表满足:
✅ 实现简单
✅ 范围查询极快(底层链表)
✅ 插入删除 O(log n) 期望
✅ 双向遍历简单
✅ 并发友好(虽然不是单线程 Redis 的核心需求)
B+树、AVL、红黑树的缺点:
❌ B+树:为磁盘设计,内存中无优势,实现复杂
❌ AVL:范围查询慢,旋转复杂
❌ 红黑树:范围查询慢,实现复杂
一句话:跳表是用略高的内存开销(多层指针)和概率性,换来了实现简单、范围查询快、并发友好,这对于内存中的有序集合来说是性价比最高的选择。
浙公网安备 33010602011771号