c++标准库未提供跳表,需手写或引入第三方;手写关键在概率化层级生成与智能指针管理;适用场景为高并发、范围查询密集或超大数据量,否则优先用std::set。

跳表在 C++ 里没有标准库实现
标准 std::set 和 std::map 是红黑树,std::unordered_set 是哈希表——C++ 标准库确实没提供跳表(Skip List)。想用跳表,得自己写或引入第三方,比如 absl::container::btree_set 是 B-tree,不是跳表;folly::F14NodeSet 是哈希变种,也不是。别在 #include <map></map> 里找 skip_list,它不存在。
手写跳表的关键控制点:层级概率和指针管理
跳表性能依赖随机层数,常见错误是用 rand() % MAX_LEVEL 均匀取层——这会让高层节点过多,退化成链表。正确做法是用概率(如 0.5)逐层“抛硬币”:
int randomLevel() {
int level = 1;
while (rand() < RAND_MAX / 2 && level < MAX_LEVEL) level++;
return level;
}
另外,C++ 里指针必须手动管理:Node* 容易悬空,尤其删除时要从所有层级断开。建议用 std::unique_ptr<node></node>,但注意不能直接赋值给裸指针数组——得用 get() 取地址,释放交给智能指针。
- 插入前必须从最高层开始向下搜索,每层更新
update[i]记录插入位置前驱 - 删除时若某层无该节点,
update[i]对应指针不变,不能跳过检查 -
MAX_LEVEL别设太大(如 64),16~32 足够,否则内存浪费且缓存不友好
什么时候真该用跳表,而不是 std::set?
跳表优势不在“更快”,而在**并发友好**和**范围查询的局部性**。如果你的场景满足以下任意一条,才值得上手写:
立即学习“C++免费学习笔记(深入)”;
- 需要高并发读写,且不想用
std::shared_mutex锁整个std::set - 频繁做
lower_bound+ 连续遍历(比如日志时间窗口扫描),跳表的多层指针能减少跳转次数 - 数据量极大(亿级)、内存带宽受限,跳表比红黑树更缓存友好(局部连续访问)
反之,如果只是查单个 key、数据量在百万以内、线程少于 4 个,std::set 更稳——它经过几十年优化,而你写的跳表可能在 erase 后迭代器失效都没处理好。
调试跳表最容易崩的三个地方
崩溃往往不出现在 insert,而出现在 delete 或迭代器遍历时:
-
Segmentation fault在node->forward[i] == nullptr检查漏掉,尤其是i超出当前节点实际层数时 - 插入后某层指针没更新,导致后续查找“穿墙”跳过目标节点(现象:
find()返回nullptr,但dump()显示节点明明存在) - 拷贝构造/移动构造没实现,用
std::vector<skiplist></skiplist>存多个实例时,析构二次释放同一块内存
建议在每个指针操作前后加 assert(node != nullptr),哪怕只在 debug 版本里。跳表结构看着松散,其实每根指针都卡着逻辑命门。










