数据结构与算法——跳跃表

发布时间:2026/9/27 11:31:21

数据结构与算法——跳跃表 文章目录一、跳跃表概述二、跳跃表算法实现一、跳跃表概述跳跃表Skip List是一种概率性数据结构它就像是升级版的有序链表专门用来实现有序集合的功能。它通过引入多层索引来提高查找、插入和删除操作的效率使得这些操作的时间复杂度可以达到 O(log⁡n)其效率可以与平衡二叉搜索树相媲美。跳跃表的核心思想是通过随机化来维护多层索引从而避免像平衡树那样复杂的平衡操作。随机性决定节点层数在跳跃表中每个节点的层数是随机确定的。当插入一个新节点时算法会根据一个随机过程来决定该节点应该拥有多少层。通常这个随机过程基于抛硬币的思想比如抛一次硬币正面则该节点的层数加 1继续抛硬币直到出现反面为止。这种随机性使得跳跃表在构建时不需要预先知道数据集的大小和分布它会在动态插入和删除元素的过程中自动调整结构。平均性能而非最坏性能保证跳跃表通过随机化的方式来平衡其结构从而在平均情况下达到较好的性能。虽然在最坏情况下跳跃表的性能可能会退化为普通链表的性能例如所有节点的层数都为 1但这种情况发生的概率非常低。它的平均时间复杂度为 O(logn)这里的平均是基于随机算法的期望性能而不是对所有可能输入都能保证的最坏情况性能。有序性跳跃表中的元素是按照键值有序排列的。就像有序链表一样每个节点都有一个键可以理解为元素的值并且所有节点的键是按照从小到大或自定义的顺序排列的。这种有序性使得跳跃表可以高效地支持范围查询等操作例如查找某个范围内的所有元素。支持多种操作跳跃表可以实现有序集合所需的基本操作如插入、删除和查找。插入操作新元素会按照其键值的大小插入到合适的位置并且根据随机过程确定该元素节点的层数。删除操作先找到要删除的节点然后调整指针将其从跳跃表中移除同时保持跳跃表的有序性。查找操作利用跳跃表的多层结构查找过程可以通过高层指针快速跳过大量节点从而减少查找所需的比较次数提高查找效率。1.1 节点结构跳跃表是在有序链表的基础上发展而来的。为了提高链表的查找效率跳跃表会随机地为每个节点增加额外的指针这些指针可以跳过一些中间节点从而加快查找速度。每个节点可以有不同的层次层次越高该节点的指针可以跳过的节点数就越多。跳跃表的每个节点包含以下信息key键用于标识和排序元素的唯一标识。在插入新节点时会根据 key 的大小将节点插入到合适的位置以保证跳跃表的有序性。在搜索操作中也是根据 key 来确定要查找的元素位置。通常要求 key 是唯一的即跳跃表中不会存在两个 key 相同的节点。这样可以确保在搜索时能够准确地定位到一个节点。value值value 是与 key 关联的数据它存储了用户真正需要的数据信息。value 的类型可以根据具体需求进行定义比如整数、字符串、自定义对象等。当通过 key 找到对应的节点后就可以获取该节点的 value。层数当前节点所在的层数。指针数组它存储了该节点在不同层次上的后继节点的指针class SkipListNode { public: int key; int value; int level; SkipListNode** forward; SkipListNode(int key, int value, int level) : key(key), value(value), level(level) { forward new SkipListNode * [level 1]; for (int i 0; i level; i) { forward[i] nullptr; } } ~SkipListNode() { delete[] forward; } };1.2 层数跳跃表是一种分层的数据结构由多个有序链表组成其中高层链表是底层链表的子集。每一层的链表都是有序的且高层链表的节点间隔更大这使得在查找元素时可以通过高层链表快速跳过大量节点从而提高查找效率。在跳跃表中每个节点的 forward 数组记录的是该节点在不同层级链表上向前指向的后继节点。可以把跳跃表想象成一条道路每个节点沿着道路向前移动forward 数组就像是指引前进方向的路标告诉我们从当前节点向前可以到达哪些后续节点。forward[i] 表示该节点在第 i 层的后继节点指针。我们使用下面这个层数为 3 的跳跃表示例来为大家讲解一下当前节点和它的 forward 数组的关系第 2 层: 1 ---------------- 5 ----------- 8 - nullptr第 1 层: 1 ------ 3 ------ 5 ------ 7-- 8 - nullptr第 0 层: 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - nullptr对于跳跃表中第 1 个节点的 forward 数组的分析forward[0]在第 0 层节点 1 的下一个节点也是 2所以 forward[0] 同样指向节点 2。forward[1]在第 1 层节点 1 的下一个节点是 3所以 forward[1] 指向节点 3。可以想象成在第二层的快速路上从节点 1 直接跳到了节点 3。forward[2]在第 2 层节点 1 的下一个节点是 5所以 forward[2] 指向节点 5。这就像在最高层的超级快速路上从节点 1 一下子跨越到了节点 5。对于跳跃表中第 3 个节点的 forward 数组的分析forward[0]在第 0 层节点 3 的下一个节点是 4所以 forward[0] 指向节点 4。forward[1]在第 1 层节点 3 的下一个节点是 5所以 forward[1] 指向节点 5。forward[2]由于节点 3 没有出现在第 2 层那么在代码中通常会将 forward[2] 设为 nullptr表示在这一层没有后继节点。1.3 随机化层数每个节点的层数是随机生成的通常需要保证高层的节点数量逐渐减少。例如第 i层的节点数量大约是第 i−1层的一半。伯努利分布是一种离散概率分布它描述了只有两种可能结果的随机试验通常标记为成功取值为 1和失败取值为 0。在伯努利试验中每次试验成功的概率为 p失败的概率为 1 - p。std::bernoulli_distribution 是 C 标准库 random 头文件中提供的一个随机数分布类用于生成服从伯努利分布的随机布尔值。#include iostream #include random int main() { // 创建一个随机数引擎 std::random_device rd; std::mt19937 gen(rd()); // 创建一个伯努利分布对象成功概率为 0.7 std::bernoulli_distribution d(0.7); // 进行 10 次随机试验 for (int i 0; i 10; i) { bool result d(gen); std::cout (result ? Success : Failure) std::endl; } return 0; }二、跳跃表算法实现2.1 跳跃表定义class SkipList { public: SkipList(); ~SkipList(); SkipListNode* search(int key, std::functionvoid(int, SkipListNode*) updateFunc nullptr); void insert(int key, int value); bool remove(int key); void traverse(); private: int randomLevel(); void saveNode(int pos, SkipListNode* node, SkipListNode** update); private: SkipListNode* m_head; int m_level; std::mt19937 m_gen; std::bernoulli_distribution m_dist; static const int MAX_LEVEL 16; };2.2 数据查找构造函数和析构函数SkipList::SkipList() : m_level(0), m_head(new SkipListNode(-1, -1, MAX_LEVEL)) { // 初始化随机数种子 random_device dev; m_gen.seed(dev()); } SkipList::~SkipList() { SkipListNode* current m_head; while (current ! nullptr) { SkipListNode* next current-forward[0]; cout 释放节点值: current-value endl; delete current; current next; } }查找算法SkipListNode* SkipList::search(int key, functionvoid(int, SkipListNode*) updateFunc) { SkipListNode* current m_head; for (int i m_level; i 0; --i) { while (current-forward[i] ! nullptr current-forward[i]-key key) { current current-forward[i]; } if (updateFunc) { updateFunc(i, current); } } current current-forward[0]; if (current ! nullptr current-key key) { return current; } return nullptr; }2.3 数据添加int SkipList::randomLevel() { int level 1; while (m_dist(m_gen) level MAX_LEVEL) { level; } return level; } void SkipList::insert(int key, int value) { // update 数组用于记录在每一层需要更新的节点 SkipListNode* update[MAX_LEVEL1]; auto func bind(SkipList::saveNode, this, placeholders::_1, placeholders::_2, update); SkipListNode* current search(key, func); if (current ! nullptr) { current-value value; } else { int newLevel randomLevel(); if (newLevel m_level 1) { newLevel m_level 1; } if (newLevel m_level) { update[newLevel] m_head; m_level newLevel; } SkipListNode* newNode new SkipListNode(key, value, newLevel); for (int i 0; i newLevel; i) { newNode-forward[i] update[i]-forward[i]; update[i]-forward[i] newNode; } } }2.4 数据删除bool SkipList::remove(int key) { SkipListNode* update[MAX_LEVEL1]; auto func bind(SkipList::saveNode, this, placeholders::_1, placeholders::_2, update); SkipListNode* current search(key, func); if (current ! nullptr) { for (int i 0; i current-level; i) { update[i]-forward[i] current-forward[i]; } delete current; while (m_level 0 m_head-forward[m_level] nullptr) { m_level--; } return true; } return false; }
延伸阅读

更多相关文章

2026/9/27 11:31:21

高精度通用时间测量频率计模块设计与等精度测量方案解析

频率计这活儿,听起来简单——不就是数脉冲吗?真做起来,采样率、时基、触发、校准这些细节摞在一起,就变成一个让人头疼的“高精度通用时间测量”工程。我最近刚把一个频率计模块方案从需求定型到硬件落地完整跑了一遍,…

2026/9/27 11:31:21

AI编程实战:RS485与LoRa设备参数调试工具快速开发

上个月我在仓库里蹲了一下午,干了一件特别无语的事:给一箱带LoRa模块的采集终端挨个配频点和发射功率,旁边还躺着一台RS485电表等着改互感器倍率。手边只有一个串口助手和一本写满寄存器地址的PDF手册,一条条AT指令敲过去&#xf…

2026/9/27 12:16:23

3步搞定wordpress上传下载,避开被黑挂马陷阱

3步搞定wordpress上传下载,避开被黑挂马陷阱 网站突然打不开,打开全是乱七八糟的弹窗,甚至直接挂了博彩广告?别慌,这大概率不是服务器挂了,而是你的wordpress上传下载配置出了漏洞。很多站长遇到这种情况,第一反应是删库重建,或者…

2026/9/27 12:16:23

模板网站演示站点怎么做避免被坑的高阶最佳实践

模板网站演示站点怎么做避免被坑的高阶最佳实践 找建站公司怕被坑高价?别急,先看看你的演示站是不是裸奔。很多甲方在验收“模板网站演示站点怎么做”这个环节时,只盯着页面好不好看,忽略了后台安全。一旦演示站上线,黑客脚本就在扫描端口。今天咱们不聊…

2026/9/27 12:16:23

2026最新一个网站需要多少钱?避开模板坑的实战报价单

2026最新一个网站需要多少钱?避开模板坑的实战报价单 很多老板一上来就问:“做个官网大概要多少钱?” 这时候如果你只回一个数字,比如“八千”或者“三万”,其实是在误导自己。 模板网站太丑不够用…

2026/9/27 12:11:22

百度网站前面的图片怎么做才吸睛?保姆级建站教程拆解

百度网站前面的图片怎么做才吸睛?保姆级建站教程拆解 网站做好了没人访问,往往不是代码写得烂,而是第一眼没抓住眼球。很多站长盯着后台数据发愁,流量卡在个位数,其实问题出在那些看似不起眼的细节上。这篇保姆级建站教程,咱们不聊虚的,直接拆解怎么把…

2026/9/27 0:00:45

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/9/27 0:00:45

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/27 0:00:45

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/27 0:00:45

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/9/27 0:00:45

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/27 0:00:45

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/25 20:55:38

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/26 19:58:38

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/25 18:34:56

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑