跳表通过多层有序链表与概率化层数实现O(log n)查找:单层有序链表查找为O(n),因只能逐个遍历;跳表在底层之上构建稀疏“快车道”,从最高层开始向右跳跃、再降层,模拟二分过程,避免严格2:1比例导致的插入/删除开销。

跳表(SkipList)不是靠树形结构或哈希函数实现 O(log n) 查找的,而是用「多层有序链表 + 概率化层数」模拟二分过程——它不强制每层节点数精确为下一层的一半,而是靠随机生成层数来摊平期望性能。
为什么单层有序链表查得慢,而跳表能加速?
单链表只能从头开始逐个比较,查找时间是 O(n)。跳表在底层链表(Level 0)之上叠加若干层“快车道”:上层节点更稀疏,指针跨得更远。查找时从最高层出发,一路向右跳到“下一个值 > target”的位置,再下降一层继续,最终落到 Level 0 判断是否存在。这个过程近似于二分查找的“折半”逻辑,但不用数组索引,只靠指针跳跃。
关键点在于:它不要求每层严格 2:1 对应,否则插入/删除一个节点就得重排整段链表,退化成 O(n);而是接受概率分布,用随机性换简洁性。
randomLevel() 怎么写才合理?
常见错误是写成 rand() % MAX_LEVEL + 1——这会让各层出现概率均等,导致高层节点过多、空间浪费,且搜索路径变长。正确做法是“抛硬币式”递进:
- 从 level = 1 开始
- 每次以固定概率(如 0.5 或 Redis 的 0.25)决定是否再升一层
- 设上限
MAX_LEVEL(如 16 或 32),防止无限增长
示例实现:
int randomLevel() {
int level = 1;
while (rand() % 2 == 0 && level <p>这样第 <code>i</code> 层节点的期望数量是总节点数的 <code>1/(2^i)</code>,保证了空间复杂度仍是 <code>O(n)</code>,且搜索时横向跳转的期望次数恒定(数学上可证为常数)。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2823" title="C++14"><img
src="https://img.php.cn/upload/manual/001/431/639/6ac8b33c327c4749.png" alt="C++14" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="overflowclass">C++14</a>
<p class="overflowclass">C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2823" title="C++14" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><h3>查找时为什么要从最高层开始往下走?</h3><p>因为最高层覆盖范围最广,能最快排除大段无关区域。如果从底层开始,就退化回普通链表;如果中途跳层顺序错乱(比如先降再右),会漏掉节点或陷入死循环。</p><p>标准查找流程必须满足:</p>
- 每一层内只往右走,直到
next[i]->val >= target - 然后下降一层(
i--),重复该动作 - 最终停在 Level 0 的前驱节点,检查
next[0]是否等于target
这个“右→下→右→下”的模式,本质是在做坐标系里的曼哈顿路径逼近,确保不会跳过目标。
实际工程中容易被忽略的细节
很多初学者照着伪代码写完发现插入后查不到——问题常出在三处:
-
head节点的forward数组必须初始化为全nullptr,且大小至少为MAX_LEVEL + 1(有些实现用 0-based 层号,有些用 1-based) - 插入时的
update[]数组要记录每一层的插入位置,不能只记某一层;否则高层链接会断开 -
srand(time(nullptr))必须只调用一次(通常在main()开头),否则在快速连续测试中rand()会返回相同序列,导致所有节点层数都为 1
跳表真正的难点不在原理,而在指针链接的严密性和边界条件处理——少一个 != nullptr 判断,就可能段错误;少一次 update[i] = p 赋值,插入就静默失败。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










