C++怎么实现跳表_C++高效查找教程【替代】

7次阅读

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

C++怎么实现跳表_C++高效查找教程【替代】

跳表在 C++ 里没有标准库实现

标准 std::setstd::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 faultnode->forward[i] == nullptr 检查漏掉,尤其是 i 超出当前节点实际层数时
  • 插入后某层指针没更新,导致后续查找“穿墙”跳过目标节点(现象:find() 返回 nullptr,但 dump() 显示节点明明存在)
  • 拷贝构造/移动构造没实现,用 std::vector<skiplist></skiplist> 存多个实例时,析构二次释放同一块内存

建议在每个指针操作前后加 assert(node != nullptr),哪怕只在 debug 版本里。跳表结构看着松散,其实每根指针都卡着逻辑命门。

text=ZqhQzanResources