数据结构 - 跳表 Skip List

news/2025/10/4 20:55:43/文章来源:https://www.cnblogs.com/wenbinteng/p/19125921

跳表(Skip List)是一种用于查找的类似于链表的数据结构,是对有序链表的改进,能够在 \(O(\log{n})\) 时间内完成增加、删除、查找操作。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短。

1. 什么是跳表

跳表包含多个层级,每个层级包含一个有序的短链表。在跳表中进行查找,遵循以下步骤:

  1. 从跳表的最高层开始查找。
  2. 对于该层的有序链表,水平地逐个比较节点,直至当前节点值大于等于目标值。如果等于目标值,则返回结果;如果大于目标值,则移动至下一层进行查找。
  3. 如果在第一层仍无法找到目标值,则说明对应节点不存在。

使用这种分层查找,可以跳过链表中没有必要的比较,加快查询速度。

2. 跳表的基本操作

跳表一般需要支持增加、删除、查找操作。

2.1 节点定义

节点除了一个存储值的变量外,还包含一个数组用于存储每一层指向下一个节点的指针。

class SkiplistNode {int val;std::vector<SkiplistNode *> forward;SkiplistNode(int _val, int _maxLevel = MAX_LEVEL): val(_val), forward(_maxLevel, nullptr) {}
};
2.2 增加节点

假设我们新加入的节点为 newNode,我们计算此次节点的插入层数 lv,如果 level 小于 lv,则更新 level 的链表。在每一个需要插入节点的层中,查找节点的插入位置,即在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点。我们用数组 update 保存每一层查找的最后一个节点。我们将 newNode 的第 i 层后续节点指向 update[i] 的下一个节点,同时更新 update[i] 的后续节点为 newNode

void add(int num) {std::vector<SkiplistNode *> update(MAX_LEVEL, head);SkiplistNode *curr = this->head;for (int i = level - 1; i >= 0; i--) {while (curr->forward[i] && curr->forward[i]->val < num) {curr = curr->forward[i];}update[i] = curr;}int lv = randomLevel();level = std::max(level, lv);SkiplistNode *newNode = new SkiplistNode(num, lv);for (int i = 0; i < lv; i++) {newNode->forward[i] = update[i]->forward[i];update[i]->forward[i] = newNode;}
}
2.3 删除节点

首先我们需要查找当前元素是否存在于跳表中。从跳表的最高层中开始查找,在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点,然后移动至下一层进行查找,重复这个过程直至到达第一层。如果在第一层仍无法找到目标值,则说明对应节点不存在。我们用数组 update 保存每一层查找的最后一个节点。我们从第一层开始逐层向上删除链表中的节点,直至该层的链表中没有目标节点。

bool erase(int num) {std::vector<SkiplistNode *> update(MAX_LEVEL, nullptr);SkiplistNode *curr = this->head;for (int i = level - 1; i >= 0; i--) {while (curr->forward[i] && curr->forward[i]->val < num) {curr = curr->forward[i];}update[i] = curr;}curr = curr->forward[0];if (!curr || curr->val != num) {return false;}for (int i = 0; i < level; i++) {if (update[i]->forward[i] != curr) {break;}update[i]->forward[i] = curr->forward[i];}delete curr;while (level > 1 && head->forward[level - 1] == nullptr) {level--;}return true;
}
2.4 查询节点

从跳表的最高层开始查找,在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点,然后移动至下一层进行查找,重复这个过程直至到达第一层。

bool search(int target) {SkiplistNode *curr = this->head;for (int i = level - 1; i >= 0; i--) {while (curr->forward[i] && curr->forward[i]->val < target) {curr = curr->forward[i];}}curr = curr->forward[0];if (curr && curr->val == target) {return true;}return false;
}

3. 跳表的复杂度分析

  • 时间复杂度:对于一个节点,其向更高层走的概率为 \(p\),该层为最高层的概率为 \(1-p\),设 \(C(i)\) 为在一个无限长度跳表中向上走 \(i\) 层的期望代价,则 \(C(i) = (1-p)(1+C(i)) + p(1+C(i-1))\),解得 \(C(i)=\frac{1}{p}\)。设 \(L(n)\) 为含有 \(n\) 个节点的跳表中的最大层数,其包含的元素个数期望为 \(\frac{1}{p}\),则 \(\frac{1}{p}=np^{L(n)-1}\),解得 \(L(n)=\log_{p}{\frac{1}{n}}\)。现在我们得知,在长度为 \(n\) 的跳表中,从最底层爬到第 \(L(n)\) 层的期望步数存在上界 \(\frac{L(n)-1}{p}\);到达第 \(L(n)\) 层后,向左走的步数不会超过第 \(L(n)\) 层及更高层的节点数总和,而这个总和的期望为 \(\frac{1}{p}\)。所以,到达第 \(L(n)\) 层后,向左走的期望步数存在上界 \(\frac{1}{p}\);同理,向上走的期望步数存在上界 \(\frac{1}{p}\)。因此,跳表查询的期望步数为 \(\frac{L(n)-1}{p}+\frac{2}{p}\),即期望时间复杂度为 \(O(\log{n})\)。最坏情况下,每一层的链表长度等于原始链表,此时跳表的最坏时间复杂度为 \(O(n)\)

  • 空间复杂度:对于一个节点,其最高层数为 \(i\) 的概率为 \(p^{i-1}(1-p)\),则跳表的期望层数为 \(\sum_{i \geq 1}ip^{i-1}(1-p)\)。因为 \(p\) 是常数,所以跳表的平均空间复杂度为 \(O(n)\)。在最坏的情况下,每一层的链表长度等于原始链表,此时跳表的最坏空间复杂度为 \(O(n\log{n})\)

4. 跳表的模版

constexpr int MAX_LEVEL = 32;
constexpr double P_FACTOR = 0.25;class SkiplistNode {public:int val;std::vector<SkiplistNode *> forward;SkiplistNode(int _val, int _maxLevel = MAX_LEVEL): val(_val), forward(_maxLevel, nullptr) {}
};class Skiplist {private:SkiplistNode *head;int level;public:Skiplist() : head(new SkiplistNode(-1)), level(0) {}bool search(int target) {SkiplistNode *curr = this->head;for (int i = level - 1; i >= 0; i--) {while (curr->forward[i] && curr->forward[i]->val < target) {curr = curr->forward[i];}}curr = curr->forward[0];if (curr && curr->val == target) {return true;}return false;}void add(int num) {std::vector<SkiplistNode *> update(MAX_LEVEL, head);SkiplistNode *curr = this->head;for (int i = level - 1; i >= 0; i--) {while (curr->forward[i] && curr->forward[i]->val < num) {curr = curr->forward[i];}update[i] = curr;}int lv = randomLevel();level = std::max(level, lv);SkiplistNode *newNode = new SkiplistNode(num, lv);for (int i = 0; i < lv; i++) {newNode->forward[i] = update[i]->forward[i];update[i]->forward[i] = newNode;}}bool erase(int num) {std::vector<SkiplistNode *> update(MAX_LEVEL, nullptr);SkiplistNode *curr = this->head;for (int i = level - 1; i >= 0; i--) {while (curr->forward[i] && curr->forward[i]->val < num) {curr = curr->forward[i];}update[i] = curr;}curr = curr->forward[0];if (!curr || curr->val != num) {return false;}for (int i = 0; i < level; i++) {if (update[i]->forward[i] != curr) {break;}update[i]->forward[i] = curr->forward[i];}delete curr;while (level > 1 && head->forward[level - 1] == nullptr) {level--;}return true;}int randomLevel() {int lv = 1;while (random() < P_FACTOR && lv < MAX_LEVEL) {lv++;}return lv;}
};

5. 实例

1206. 设计跳表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/927545.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

06. 定时器

一、定时器QML 有一个 Timer元素,它允许你在 QML 中设置定时器。这个元素是 Qt Quick 模块的一部分。我们可以通过定时器的 interval 属性设置 定时间隔,通过 running 属性设置 定时器默认是否运行,通过 repeat 属性…

高端集团网站建设公司铭万做的网站怎么样

11.盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾…

怎么自己建立网站站长之家seo

前言 Gitee 是一个中国的开源代码托管平台&#xff0c;类似于 GitHub&#xff0c;旨在为开发者提供一个高效、稳定、安全的代码管理和协作开发环境。Gitee 支持 Git 协议&#xff0c;可以托管 Git 仓库&#xff0c;进行版本控制、代码协作、项目管理等操作。 1. Gitee 的主要…

职业学院网站建设网站建设需要哪些资质

这是渲染的数据 这是生成的pdf文件&#xff0c;直接可以打印 需要安装和npm依赖和引入封装的pdf.js文件 npm install --save html2canvas // 页面转图片 npm install jspdf --save // 图片转pdfpdf.js文件 import html2canvas from "html2canvas"; import jsPDF …

没人做网站了吗重庆建设网站哪家专业

ArcGIS软件可以很方便的直接实现度分秒转度、度转度分秒(度分秒→度、度→度分秒)。 文章目录 一、转换预览二、工具介绍三、案例解析一、转换预览 借助ArcGIS快速实现度分秒与度及其他格式的坐标转换,例如:度分秒→度、度→度分秒。 1. 度→度分秒 2. 度分秒→度 转换后…

硬件-电容学习DAY23——电容设计实战指南:从选型到高频应用 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

NOIP之前的复健记录

写一些做题记录,题解和随机话。 这回真是高一零基础学OI/文化课了。CF547B考虑每一个数对答案的贡献。 自然是当这个数 \(a_i\)是所选的区间最小值时对答案有贡献,所以我们找到左边第一个比 \(a_i\) 大的数 \(a_l\) …

支付网站开发建设费用怎么入账网站建设设计服务公司

了解细胞对基因扰动的反应是许多生物医学应用的核心&#xff0c;从识别癌症中涉及的基因相互作用到开发再生医学方法。然而&#xff0c;可能的多基因扰动数量的组合爆炸严重限制了实验验证。在这里&#xff0c;作者提出了图增强的基因激活和抑制模拟器&#xff08;GEARS&#x…

Linux 命令行安装达梦数据库

达梦官方不提供 docker 镜像了,由于要开发国产化项目,因此只能暂时在 Linux 服务器上直接安装。如果 Linux 操作系统带有图形化界面的话,安装起来很简单,参考官网即可,这里不再赘述。有的客户现场提供的 Linux 服…

Google开源Tunix:JAX生态的LLM微调方案来了

AX生态这两年在LLM训练这块追赶得挺快。PyTorch虽然还是主流但JAX在并行计算、TPU加速和API组合性上确实有些独特的优势。Google今天放出了Tunix这个库,专门做LLM的后训练——微调、强化学习、知识蒸馏这些都能搞。 T…

域名和空间都有了怎么做网站三门峡网站制作

原标题&#xff1a;鸿蒙OS 2.0系统正式发布&#xff01;余承东&#xff1a;明年华为系手机将会搭载由于众所周知的原因&#xff0c;华为手机这两年过的很艰难&#xff0c;尤其是今年力度大了后&#xff0c;华为手机接下来可能面临无芯片可用的严重情况。而除了一些硬件外&#…

看上去高端的网站兰州建设局网站

全文共2485字&#xff0c;预计学习时长12分钟图源&#xff1a;unsplash数据科学的生命周期主要包括数据收集、数据清理、探索性数据分析、模型构建和模型部署。作为数据科学家或机器学习工程师&#xff0c;能够部署数据科学项目非常重要&#xff0c;这有助于完成数据科学生命周…

网站服务方案2022昆明今天刚刚发生的新闻

常用类 目录 1. QString 字符串类&#xff08;掌握&#xff09; 2. 容器类&#xff08;掌握&#xff09; 2.1 顺序容器QList 2.2 关联容器QMap 3. 几种Qt数据类型&#xff08;熟悉&#xff09; 3.1 跨平台数据类型 3.2 QVariant 统一数据类型 3.3 QStringList 字符串列表 4. QD…

实用指南:如何优化 C# MVC 应用程序的性能

实用指南:如何优化 C# MVC 应用程序的性能2025-10-04 20:30 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: blo…

软件工程的第一次作业

软件工程的第一次作业这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/202501SoftwareEngineering这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/202501SoftwareEngineering/homework/13546这个作…

实用指南:Matlab通过GUI实现点云的快速全局配准(FGR)

实用指南:Matlab通过GUI实现点云的快速全局配准(FGR)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&qu…

Eclipse 中文语言包安装教程:一键将界面切换为中文 - 教程

Eclipse 中文语言包安装教程:一键将界面切换为中文 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consol…

『OI 回忆录』停课有感

原来已经快 2 年了吗。怀恋啊。时值 11.17 晚 20:11。 停课结束了。写一篇文章。\(\text{Schedule}\)开心是一天,不开心也是一天。11.11 第一次和高中联考,获得了 125 pts 的好成绩。11.11 并且得知自己去不了 NOIP,…

『回忆录』初三第三学月

呜呜呜停课 半期 我计划着每次月考完都写一篇,当做纪念。 感觉真的太快了,一眨眼就是一个月,记不起发生了什么…… 可能写得有些乱,敬请谅解。经历 上中旬的时候听说基本全员去 NOIP,于是跟着一起停课。 关于停课…

完整教程:MySQL 5.7 主主复制 + Keepalived 高可用配置实例

完整教程:MySQL 5.7 主主复制 + Keepalived 高可用配置实例pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Conso…