C++面试:跳表

        

目录

跳表介绍 

跳表的特点:

跳表的应用场景:

C++ 代码示例:

跳表的特性

跳表示例 

总结


        跳表(Skip List)是一种支持快速搜索、插入和删除的数据结构,具有相对简单的实现和较高的查询性能。下面是跳表的详细介绍和一个简单的 C++ 代码示例:

跳表介绍 

跳表的特点:

  1. 有序结构: 跳表中的每个节点都包含一个元素,并且节点按照元素的大小有序排列。
  2. 多层索引: 跳表通过维护多层索引来实现快速搜索。每一层都是一个有序链表,最底层包含所有元素,而每上一层的节点是下一层节点的一部分。
  3. 跳跃式访问: 通过索引层,跳表允许在较高层直接跳过一些节点,从而提高搜索效率。

跳表的应用场景:

  1. 有序集合的实现: 用于需要频繁的插入、删除和搜索操作的有序数据集合,如 Redis 中的有序集合(Sorted Set)。
  2. 替代平衡树: 在某些场景下,跳表可以作为对平衡树的一种替代,具有更简单的实现和较好的性能。

C++ 代码示例:

#include <iostream>
#include <vector>
#include <cstdlib>const int MAX_LEVEL = 16;  // 最大层数// 跳表节点定义
struct Node {int value;std::vector<Node*> forward;  // 每层的指针数组Node(int val, int level) : value(val), forward(level, nullptr) {}
};// 跳表定义
class SkipList {
private:Node* header;  // 头节点int level;     // 当前跳表的最大层数public:SkipList() : level(1) {header = new Node(0, MAX_LEVEL);}// 随机生成一个层数int randomLevel() {int lvl = 1;while ((rand() % 2) && (lvl < MAX_LEVEL))lvl++;return lvl;}// 插入一个元素void insert(int val) {std::vector<Node*> update(MAX_LEVEL, nullptr);Node* current = header;// 从最高层到底层,找到每一层的插入位置for (int i = level - 1; i >= 0; i--) {while (current->forward[i] != nullptr && current->forward[i]->value < val) {current = current->forward[i];}update[i] = current;}// 随机生成一个层数int newLevel = randomLevel();// 如果新的层数比当前层数高,则更新 updateif (newLevel > level) {for (int i = level; i < newLevel; i++) {update[i] = header;}level = newLevel;}// 创建新节点Node* newNode = new Node(val, newLevel);// 更新每一层的指针for (int i = 0; i < newLevel; i++) {newNode->forward[i] = update[i]->forward[i];update[i]->forward[i] = newNode;}}// 搜索一个元素,返回是否存在bool search(int val) {Node* current = header;// 从最高层到底层,搜索每一层的节点for (int i = level - 1; i >= 0; i--) {while (current->forward[i] != nullptr && current->forward[i]->value < val) {current = current->forward[i];}}// 到达底层,判断是否找到目标元素if (current->forward[0] != nullptr && current->forward[0]->value == val) {return true;} else {return false;}}// 删除一个元素void remove(int val) {std::vector<Node*> update(MAX_LEVEL, nullptr);Node* current = header;// 从最高层到底层,找到每一层的删除位置for (int i = level - 1; i >= 0; i--) {while (current->forward[i] != nullptr && current->forward[i]->value < val) {current = current->forward[i];}update[i] = current;}// 到达底层,判断是否找到目标元素if (current->forward[0] != nullptr && current->forward[0]->value == val) {// 更新每一层的指针,删除目标节点for (int i = 0; i < level; i++) {if (update[i]->forward[i] != current->forward[i]) {break;}update[i]->forward[i] = current->forward[i]->forward[i];}// 如果删除的是最高层的节点,更新层数while (level > 1 && header->forward[level - 1] == nullptr) {level--;}// 释放节点内存delete current;}}// 打印跳表void printSkipList() {for (int i = level - 1; i >= 0; i--) {Node* current = header->forward[i];std::cout << "Level " << i << ": ";while (current != nullptr) {std::cout << current->value << " ";current = current->forward[i];}std::cout << std::endl;}std::cout << "-----------------------" << std::endl;}
};int main() {// 创建跳表SkipList skipList;// 插入一些元素skipList.insert(3);skipList.insert(6);skipList.insert(7);skipList.insert(9);skipList.insert(12);// 打印跳表skipList.printSkipList();// 搜索元素int searchValue = 7;if (skipList.search(searchValue)) {std::cout << "Element " << searchValue << " found in the skip list." << std::endl;} else {std::cout << "Element " << searchValue << " not found in the skip list." << std::endl;}// 删除元素int removeValue = 6;skipList.remove(removeValue);// 打印删除后的跳表skipList.printSkipList();return 0;
}

        这是一个简单的跳表实现,包括插入、搜索和删除操作。在实际应用中,跳表的层数、随机层数的方式以及其他细节可以根据具体需求进行调整。

跳表的特性

  1. 有序性: 跳表中的每个节点按照元素的大小有序排列。这使得在跳表中可以快速定位和搜索元素。

  2. 多层索引: 跳表通过维护多层索引来实现快速搜索。每一层都是一个有序链表,最底层包含所有元素,而每一层的节点是下一层节点的子集。这样的多层索引结构可以提高搜索效率。

  3. 跳跃式访问: 通过多层索引,跳表允许在较高层直接跳过一些节点,从而实现跳跃式的访问。这种设计类似于在二分查找中直接跳过一半的元素,从而提高了搜索的效率。

  4. 平衡性: 跳表的设计通过随机层数和灵活的插入策略,保持了跳表的平衡性。这有助于避免类似于二叉搜索树中的不平衡情况,使得操作的时间复杂度更加可控。

  5. 简单实现: 跳表相对于其他高效的数据结构,如平衡树,实现相对简单。它不需要像平衡树那样复杂的平衡维护,使得代码的实现和维护相对容易。

  6. 支持动态操作: 跳表天生适合动态操作,包括插入和删除。由于插入和删除操作只需要调整相邻节点的指针,而不需要进行全局的平衡调整,因此操作的效率较高。

  7. 适应范围广: 跳表可以应用于各种有序数据集合的场景,特别是在需要频繁插入、删除和搜索操作的场景中,其性能表现优异。

        跳表的这些特性使得它在一些应用场景中具有明显的优势,尤其在无法提前知道数据分布情况的情形下,跳表能够以较简单的方式维护有序性和高效操作。

跳表示例 

        下面是一个使用 C++ 实现的跳表例子,包含插入、搜索、删除和打印操作。在这个例子中,我使用了模板类以支持不同类型的元素。

#include <iostream>
#include <vector>
#include <cstdlib>// 跳表节点定义
template <typename T>
struct Node {T value;std::vector<Node*> forward;Node(T val, int level) : value(val), forward(level, nullptr) {}
};// 跳表定义
template <typename T>
class SkipList {
private:Node<T>* header;int level;public:SkipList() : level(1) {header = new Node<T>(T(), MAX_LEVEL);  // 初始值为 T() 的头节点}// 随机生成一个层数int randomLevel() {int lvl = 1;while ((rand() % 2) && (lvl < MAX_LEVEL))lvl++;return lvl;}// 插入一个元素void insert(const T& val) {std::vector<Node<T>*> update(MAX_LEVEL, nullptr);Node<T>* current = header;// 从最高层到底层,找到每一层的插入位置for (int i = level - 1; i >= 0; i--) {while (current->forward[i] != nullptr && current->forward[i]->value < val) {current = current->forward[i];}update[i] = current;}// 随机生成一个层数int newLevel = randomLevel();// 如果新的层数比当前层数高,则更新 updateif (newLevel > level) {for (int i = level; i < newLevel; i++) {update[i] = header;}level = newLevel;}// 创建新节点Node<T>* newNode = new Node<T>(val, newLevel);// 更新每一层的指针for (int i = 0; i < newLevel; i++) {newNode->forward[i] = update[i]->forward[i];update[i]->forward[i] = newNode;}}// 搜索一个元素,返回是否存在bool search(const T& val) const {Node<T>* current = header;// 从最高层到底层,搜索每一层的节点for (int i = level - 1; i >= 0; i--) {while (current->forward[i] != nullptr && current->forward[i]->value < val) {current = current->forward[i];}}// 到达底层,判断是否找到目标元素return (current->forward[0] != nullptr && current->forward[0]->value == val);}// 删除一个元素void remove(const T& val) {std::vector<Node<T>*> update(MAX_LEVEL, nullptr);Node<T>* current = header;// 从最高层到底层,找到每一层的删除位置for (int i = level - 1; i >= 0; i--) {while (current->forward[i] != nullptr && current->forward[i]->value < val) {current = current->forward[i];}update[i] = current;}// 到达底层,判断是否找到目标元素if (current->forward[0] != nullptr && current->forward[0]->value == val) {// 更新每一层的指针,删除目标节点for (int i = 0; i < level; i++) {if (update[i]->forward[i] != current->forward[i]) {break;}update[i]->forward[i] = current->forward[i]->forward[i];}// 如果删除的是最高层的节点,更新层数while (level > 1 && header->forward[level - 1] == nullptr) {level--;}// 释放节点内存delete current;}}// 打印跳表void printSkipList() const {for (int i = level - 1; i >= 0; i--) {Node<T>* current = header->forward[i];std::cout << "Level " << i << ": ";while (current != nullptr) {std::cout << current->value << " ";current = current->forward[i];}std::cout << std::endl;}std::cout << "-----------------------" << std::endl;}
};int main() {// 创建跳表SkipList<int> skipList;// 插入一些元素skipList.insert(3);skipList.insert(6);skipList.insert(7);skipList.insert(9);skipList.insert(12);// 打印跳表skipList.printSkipList();// 搜索元素int searchValue = 7;if (skipList.search(searchValue)) {std::cout << "Element " << searchValue << " found in the skip list." << std::endl;} else {std::cout << "Element " << searchValue << " not found in the skip list." << std::endl;}// 删除元素int removeValue = 6;skipList.remove(removeValue);// 打印删除后的跳表skipList.printSkipList();return 0;
}

在这个例子中,使用跳表有几个考虑因素:

  1. 高效的搜索操作: 跳表的搜索操作时间复杂度为 O(log n),其中 n 是跳表中的元素个数。相较于普通链表的线性搜索,跳表提供了更快的搜索速度。

  2. 支持动态操作: 跳表天生适合动态操作,包括插入和删除。由于插入和删除操作只需要调整相邻节点的指针,而不需要进行全局的平衡调整,因此在元素的动态更新场景下,跳表相对于其他数据结构更具有优势。

  3. 简单实现: 跳表的实现相对简单,不需要像平衡树那样复杂的平衡维护。这使得它在实际应用中更容易实现和维护。

  4. 对比其他数据结构: 在这个示例中,使用跳表的主要目的是演示跳表的基本原理和操作,并不代表它是绝对优于其他数据结构的选择。具体选择数据结构的决策取决于实际应用场景、数据分布情况以及对不同操作的需求。

总结

特性:

  1. 有序性: 跳表中的每个节点按照元素的大小有序排列,使得在跳表中可以快速定位和搜索元素。
  2. 多层索引: 跳表通过维护多层索引来实现快速搜索,每一层都是一个有序链表,最底层包含所有元素。
  3. 跳跃式访问: 通过多层索引,跳表允许在较高层直接跳过一些节点,实现跳跃式的访问,提高搜索效率。
  4. 平衡性: 通过随机层数和灵活的插入策略,保持了跳表的平衡性,避免了类似于二叉搜索树中的不平衡情况。
  5. 支持动态操作: 跳表天生适合动态操作,包括插入和删除,操作的时间复杂度较低。

应用场景:

  1. 有序集合的实现: 适用于需要频繁插入、删除和搜索操作的有序数据集合,例如在 Redis 中的有序集合(Sorted Set)实现中使用了跳表。
  2. 替代平衡树: 在某些场景下,跳表可以作为对平衡树的一种替代,相对简单的实现和较好的性能表现使得它成为一种备选选择。
  3. 动态数据库索引: 在数据库中,跳表可以用作动态索引结构,适用于动态更新和频繁搜索的情况。
  4. 高效的动态排序: 在需要频繁的动态排序操作的场景下,跳表的性能可能优于传统的排序算法。

总体评价:

  • 优势: 跳表提供了一种在有序数据集合中实现高效的动态操作的方式,相较于平衡树结构实现较为简单,适用于需要频繁更新和搜索的场景。
  • 劣势: 跳表相对于其他数据结构可能占用更多内存,对于某些内存敏感的场景,可能不是最优选择。在一些特定的搜索密集型场景中,红黑树等平衡树结构也具有竞争力。

总体而言,跳表在一些动态、搜索密集的应用场景中表现出色,但在具体选择时,需要综合考虑数据分布、内存使用、实现难度等因素。

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

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

相关文章

职业规划,软件开发工程师的岗位任职资格

软件工程师是指从事软件开发的人&#xff0c;主要的工作涉及到项目培训和项目设计两个方面。在实际工作中&#xff0c;软件工程师是一个广义的概念&#xff0c;包括了很多与软件相关的人员。除开最基础的编程语言&#xff0c;还有数据库语言等等。从事这份工作&#xff0c;需要…

记录一下uniapp 集成腾讯im特别卡(已解决)

uniapp的项目运行在微信小程序 , 安卓 , ios手机三端 , 之前这个项目集成过im,不过版本太老了,0.x的版本, 现在需要添加客服功能,所以就升级了 由于是二开 , 也为了方便 , 沿用之前的webview嵌套腾讯IM的方案 , 选用uniapp集成ui ,升级之后所有安卓用户反馈点击进去特别卡,几…

HR人才测评,如何做技术研发人员基本素质测评?

技术研发人员的基本素质测评&#xff0c;可以从以下几个方面考虑&#xff1a; 1. 技术能力&#xff1a;首要的因素是技术能力&#xff0c;包括编程能力、算法能力、架构设计能力、代码调试和优化能力等。在测评中可以通过技术测试、编程练习、项目经验等方式来考察。 2. 学习…

Java - 深入理解加密解密和签名算法

文章目录 应用的接口安全性问题可能来源加密解密Why保护数据隐私防止未经授权的访问防止数据泄露 对称加密 VS 单向加密 VS 非对称加密一、对称加密二、单向加密&#xff08;哈希加密&#xff09;三、非对称加密 常用的对称加密算法1. AES&#xff08;高级加密标准&#xff09;…

Django从入门到精通(二)

目录 三、视图 3.1、文件or文件夹 3.2、相对和绝对导入urls 3.3、视图参数requests 3.4、返回值 3.5、响应头 3.6、FBV和CBV FBV 四、静态资源 4.1、静态文件 4.2、媒体文件 五、模板 5.1、寻找html模板 5.2、模板处理的本质 5.3、常见模板语法 5.4、内置模板函…

对称密码算法有什么优点

对称密码算法是一种加密和解密数据的方法&#xff0c;其中加密和解密使用相同的密钥。这种方法的一个关键特点是加密和解密的速度非常快&#xff0c;因此它在许多需要高速加密的应用中非常有用。 对称密码算法的优点主要在于其效率和安全性。由于加密和解密使用相同的密钥&…

自定义注解与拦截器实现不规范sql拦截(拦截器实现篇)

最近考虑myBatis中sql语句使用规范的问题&#xff0c;如果漏下条件或者写一些不规范语句会对程序性能造成很大影响。最好的方法就是利用代码进行限制&#xff0c;通过拦截器进行sql格式的判断在自测环节就能找到问题。写了个简单情景下的demo&#xff0c;并通过idea插件来将myB…

UE5 Windows打包时报错“SDK Not Found”解决方案

在Unreal Engine 5.0.3 Windows平台下打包时报错&#xff1a;“Windows的SDK未正常安装&#xff0c;而其是生成数据的必需项。请检查主工具栏中“启动“菜单SDK部分来更新SDK。” 解决方案&#xff1a; 1、打开 Visual Studio Installer&#xff0c;点击“修改”按钮&#xf…

EtherNet/IP开发:C++搭建基础模块,EtherNet/IP源代码

这里是CIP资料的协议层级图&#xff0c;讲解协议构造。 ODVA&#xff08;www.ODVA.org&#xff09;成立于1995年&#xff0c;是一个全球性协会&#xff0c;其成员包括世界领先的自动化公司。结合其成员的支持&#xff0c;ODVA的使命是在工业自动化中推进开放、可互操作的信息和…

到店商详架构变迁

一、项目背景 到店商详是平台为京东到店业务提供的专属商详页面&#xff0c;将传统电商购物路径打造成以LBS门店属性的本地生活服务交易链路。 二、架构变迁 1、 主站商详扩展点 **优点&#xff1a;**到店侧仅关注业务&#xff0c;无需过度关注服务部署、性能优化等。 **缺…

数据结构与算法(一)线性表

逻辑结构 逻辑结构有以下几种关系 没关系 一对一关系 一对多关系 多对多关系 a是b的前驱&#xff0c;c是b的后继 该四种关系有以下别名 集合 线性表 树 图 …

[IPv6]常用的几种IPv4和IPv6地址格式

记录一下常用的IPv4和IPv6地址格式 IPv4 IPv4地址是32位的&#xff0c;通常表示为“192.168.1.12”这样点分成4段的形式。 一个IP地址可以分为两部分&#xff0c;网络ID和主机ID。如何区分哪些位是网络ID&#xff0c;哪些位是主机ID&#xff0c;就需要子网掩码。 子网掩码 …

pip install flagai时出现Collecting PyYAML==5.4.1 (from flagai)等错误信息的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

HCIA——22DNS:DNS层次域名空间、域名服务器、域名解析的原理

学习目标&#xff1a; 计算机网络 1.掌握计算机网络的基本概念、基本原理和基本方法。 2.掌握计算机网络的体系结构和典型网络协议&#xff0c;了解典型网络设备的组成和特点&#xff0c;理解典型网络设备的工作原理。 3.能够运用计算机网络的基本概念、基本原理和基本方法进行…

CVE重要通用漏洞复现java phpCVE-2021-44228

在进行漏洞复现之前我们需要在linux虚拟机上进行docker的安装 我不喜欢win上安因为不知道为什么总是和我的vmware冲突 然后我的kali内核版本太低 我需要重新安装一个新的linux 并且配置网络 我相信这会话费我不少时间 查看版本 uname -a 需要5.5或以上的版本 看错了浪…

Rocky Linux 9. 3安装图解

风险告知 本人及本篇博文不为任何人及任何行为的任何风险承担责任&#xff0c;图解仅供参考&#xff0c;请悉知&#xff01;本次安装图解是在一个全新的演示环境下进行的&#xff0c;演示环境中没有任何有价值的数据&#xff0c;但这并不代表摆在你面前的环境也是如此。生产环境…

计算机网络-分层结构,协议,接口,服务

文章目录 总览为什么要分层怎样分层正式认识分层概念小结 总览 为什么要分层 发送文件前要做的准备工作很多 把这个准备工作分层小问题解决&#xff0c;也就分层解决 怎样分层 每层相互独立&#xff0c;每层做的工作不同 界面自然清晰&#xff0c;层与层之间的接口能够体现…

三、Flask学习之BootSrap

三、Flask学习之BootSrap Bootstrap 是一款由Twitter团队开发的开源前端框架&#xff0c;它以响应式设计、移动端友好和丰富的组件为特色&#xff0c;为开发者提供了快速构建现代化网站和Web应用的工具。借助其灵活的栅格系统、丰富的UI组件和可定制的样式&#xff0c;Bootstr…

使用Dockerfile来构建服务的镜像,并部署在容器中

构建服务镜像和容器化部署 一、构建服务镜像1、编写Dockerfile2、构建脚本3、启动脚本 二、问题及解决办法1、no main manifest attribute, in /chatgpt-api-1.0-SNAPSHOT.jar2、如果是SpringBoot项目&#xff0c;应该这么做&#xff1a; 一、构建服务镜像 1、编写Dockerfile …

【web 编程技术】基于 B/S 架构的电商平台(java web)

基于 B/S 架构的电商平台&#xff08;java web&#xff09; 课程设计实验目的课程设计实验环境课程设计功能概述课程设计需求分析三层架构图功能列表系统用例图系统活动图-用户端需求分析 课程设计详细设计实现过程数据库BaseServlet 的实现商品显示模块-分页显示所有商品、查看…