C语言实现跳表(附源码)

最近在刷一些链表的题目,在leetcode上有一道设计跳表的题目,也是通过查阅各种资料,自己实现出来,感觉这是种很神奇的数据结构。

一.简介

跳表与红黑树,AVL树等,都是一种有序集合,那既然是有序集合,其目的肯定是去奔着提升查找效率而去实现的。

1. 单链表

看下图,比如我要查找1,在链表中第一下就能找到,而要去查找5的话,则是需要遍历完整个链表才能查找到,时间复杂度是O(n)注意如果是增删改的前提不就是需要先查找吗?所以时间复杂度是同样的。
在这里插入图片描述
然而我们之前学习的查找算法中,二分查找是非常厉害的,时间复杂度可以到达O(log n),对数级的时间复杂度相当的快,那么二分思想就是折半,像红黑树,AVL树,B树之类的数据结构,在搜索的时候都是进行折半的搜索,而跳表同样也是O(log n)的时间复杂度。

2. 跳表

如果需要查找5这个节点,在单链表中需要查找5次,而在下面的跳表中,则需要查找3次就好了,少了一次,可是真的就少一次吗?
在这里插入图片描述
拿如果节点多,层数开始往上叠加,就会发现,从1到5,直接少了5次比较。
在这里插入图片描述
经过一系列的数学证明,它的时间复杂度也是O(log n)的,但是这里肯定就不去证明了😓。
而跳表的结构就是一层一层的,拿空间换取时间。

二. 跳表的结构模型

从上图可以看出,跳表是一层一层的,所以可以用一个需要用到数组来维护。

1. 结构定义

#define MAX_LEVEL 3typedef struct SkipNode
{int val;		//值int maxLevel;	//当前节点的最大层数//下一个节点的指针数组。struct SkipNode** next;
}SkipNode;typedef struct
{int nodeNum;	//节点个数int level;		//跳表的索引总层数SkipNode* head;
}SkipList;

以上是跳表的结构定义,其中那个Node中maxLevel就是当前这个节点的层数,因为每个节点的层数是不一样的嘛,这个用途呢在后面的删除节点中会用到。
在这里插入图片描述

2. 操作函数

下面是针对与跳表的一些操作函数,其中GetRandomLevel这个函数也是我第一次学到,后面进行单独的讲解。
对于跳表的打印函数也没有,是我自己整出来的,方便调试,毕竟都是指针,谁看谁不迷糊啊。

//创建出一个新的节点,将其层数以及值传过来。
SkipNode* BuyNode(int level, int val);//创建跳表
SkipList* Create();//传过来一个 target,看看是否在跳表中
bool Search(SkipList* list, int target);//获取拆入节点时候,所需的层数
int GetRandomLevel();//将val 插入 跳表中去,
void SkipListAdd(SkipList* list,int val);//找到节点然后删除
void SkipListDel(SkipList* list, int target);//打印一下跳表结构
void Print(SkipList* list);//销毁跳表
void Destroy(SkipList** list

三. 实现操作函数

1. 获取层数(GetRandomLevel)

这个函数的实现也就是短短几行,但是不理解它,很懵,真的很懵,这个函数是获取一个随机的层数,用来开辟新节点的层数。
也能从上述的图片中发现一个问题,就是随着每一个节点的插入,我们改如何取其节点的层数是多少?
每一层呢是一个概率问题,从得二层开始,二分之一,三分之一,四分之一,五分之一等等。。

  • 我随机出来一个数这个数只能是0和1,拟定0为当前层,1为下一层.
  • 如果我这个数是0,那么就在当前层停下来
  • 如果是1,那么就去下一层,接着再随机,使其变成0的时候停下来。
  • 然后取当前所随机的层数,要是随机层数大于了最大的层数
  • 取当前跳表的层数即可。
  • (这里的最大层数是你在文件中所定义的常量 – MAX_LEVEL,而不是说当前跳表的层数)
    下面的动图举了两个例子,分别是2,和3节点。
    节点2,一下子就随机到了0,所以选择1层插入就好了
    节点3,随机了两次不是0,所以自己就加到了3,第三次是0,那么就在选择三层。
    在这里插入图片描述

2. 初始化跳表

  • 首先对head进行一个BuyNode,这样子就能通过head找到后续的全部节点。
  • 然后在对head -> next[i] 就像链表一样,设置一个头节点,这样子方便后续的一些操作。
  • 就是下面这两幅图中的样子。
    在这里插入图片描述
    在这里插入图片描述
//创建出一个新的节点,将其层数以及值传过来。
SkipNode* BuyNode(int level, int val)
{SkipNode* newNode = (SkipNode*)malloc(sizeof(SkipNode));newNode->val = val;newNode->maxLevel = level;newNode->next = (SkipNode**)malloc(sizeof(SkipNode*) * level);for (int i = 0; i < level; i++){newNode->next[i] = NULL;}return newNode;
}//创建跳表
SkipList* Create()
{SkipList* list = (SkipList*)malloc(sizeof(SkipList));list->head = BuyNode(MAX_LEVEL, -1);	//最开始初始化开辟5层,可修改,-1无意义,头节点。list->level = 0;	//初始化跳表,当前层数为0.list->nodeNum = 0;	//初始化节点个数。SkipNode* headNode = BuyNode(MAX_LEVEL, -1);for (int i = 0; i < MAX_LEVEL; i++){list->head->next[i] = headNode;}return list;
}

3. 插入

对于跳表的插入,其实也是相当于一次查找,所以只要会插入了,就肯定会查找了。
假设跳表是这个样子,需要插入4这个节点。
在这里插入图片描述

  • 首先呢我们从最高增往下去找,利用cur指针移动,
  • 在移动的过程中同时需要拿一个数组prevNodes记录着每一层的前一个节点,然后随着cur的遍历,终究会在最后一层停下来。
    在这里插入图片描述
  • 而停下之后,讲意味着找到合适的位置,所以在当前的位置下进行插入节点就好了,而prevNodes就起到了可以是前后链接的作用而链接就跟普通的链表插入一样。

以下是代码,其中还有写细节注释

//将val 插入 跳表中去,
void SkipListAdd(SkipList* list, int val)
{//也是从最高层开始int levelIndex = list->level - 1;SkipNode* cur = list->head->next[levelIndex];//开辟一个prev数组,其里面存放着每一层相对应的前一个节点。SkipNode** prevNodes = (SkipNode**)malloc(sizeof(SkipNode*) * MAX_LEVEL);	int i;for (i = levelIndex; i >= 0; i--){while (cur->next[i] != NULL && cur->next[i] -> val < val){cur = cur->next[i];}//至此呢,要么找到了当前层数的末尾,要么是找到了合适的位置prevNodes[i] = cur;}//获取随机层数int suitLevel = GetRandomLevel();if (suitLevel > list->level){//当新节点的层数比当前层数大时候,将为赋值的prevNodes[i]记录for (i = list -> level; i < suitLevel; i++){prevNodes[i] = list->head->next[i];}//更新层数list->level = suitLevel;}//将前面每层的节点于新节点进行链接SkipNode* newNode = BuyNode(suitLevel, val);for (i = 0; i < suitLevel; i++){newNode->next[i] = prevNodes[i]->next[i];prevNodes[i]->next[i] = newNode;}list->nodeNum++;
}

4. 删除

删除于插入是十分类似的,都是以相同的方式去遍历跳表,同样都是拿prevNodes记录每一层的前一个节点。

  • 删除有一种情况就是说,需要删除的数在最高层,那么此时我们需要进行检查,判断时候需要讲那一层删除掉。
  • 下图两幅图中,分别对9进行删除,如果删除之后,最高层指向的下一个不是空指针,那么就不需要删除层数,否则就需要讲层数减1
    在这里插入图片描述
    在这里插入图片描述
//找到节点然后删除
void SkipListDel(SkipList* list, int target)
{if (!Search(list, target)){printf("%d -> 此节点未找到!\n", target);return;}int levelIndex = list->level - 1;SkipNode** prevNodes = (SkipNode**)malloc(sizeof(SkipNode*) * MAX_LEVEL);SkipNode* cur = list->head->next[levelIndex];int i;for (i = levelIndex; i >= 0; i--){while (cur->next[i] != NULL && cur->next[i]->val < target){cur = cur->next[i];}prevNodes[i] = cur;}cur = cur->next[0];//将所需要删除节点的以一个和后一个链接起来for (i = 0; i < cur->maxLevel; i++){prevNodes[i]->next[i] = cur->next[i];}//判断删除当前节点后,是否需要更新最高层for (i = list -> level - 1; i >= 0; i--){if (list->head->next[i]->next[i] != NULL){break;}list->level--;}free(cur);list->nodeNum--;
}

5. 查找

其实我们在进行插入和删除同时就是在反复的做着查找的工作,在遍历的过程中判断合适的位置,重复的去比较大小。

  • 如果cur -> next[i] == NULL,直接进入下一层,也就是对循环体进行一个continue;
  • 那么如果cur -> next[i] == val, 那么就是找到了。
//传过来一个 target,看看是否在跳表中
bool Search(SkipList* list, int target)
{//从最上层开始去找int levelIndex = list->level - 1;SkipNode* cur = list->head->next[levelIndex];int i;for (i = levelIndex; i >= 0; i--){//下一个如果小于target,就往前一直遍历while (cur->next[i] != NULL && cur->next[i]->val < target){cur = cur->next[i];}//至此,要么大于,等于,或者使这一层没有。if (cur->next[i] == NULL){//直接去下一层continue;}//再去判断是否等于if (cur->next[i]->val == target){return true;}}return false;
}

6. 销毁

  • 销毁跳表的话只能是从第一层了,可不能再从上往下了。
//销毁跳表
void Destroy(SkipList** list)
{//从最底层往上SkipNode* cur = (*list)->head -> next[0];SkipNode* tmp = cur->next[0];free((*list)->head);while (cur != NULL){tmp = cur->next[0];free(cur);cur = tmp;}free(*list);*list = NULL;
}

至此呢,跳表就是实现完成了,这篇文章也是仅供参考,可能有些测试不准确,或者没有测试到位,有bug欢迎各位在评论区指出。。。

源码链接

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

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

相关文章

Prompt Engneering(提示词工程)

大模型的目标是理解和生成人类语言。给定一些词语&#xff0c;语言模型可以预测下一个词语可能是什么&#xff0c;或者给定的一些词语生层对应内容。 那么如何引导它们产生期望的输出&#xff0c;或者说如何提问&#xff0c;就成为了一个关键的问题。这就引入了一个重要的概念…

QStandardItemModel与QTableView里如何设置复选框居中

笔者为这个问题思索了不少时间&#xff0c;这个问题就是c qt里创建了一个QStandardItemModel设置了表格的表头&#xff0c;往表格填充数据时&#xff0c;数据的复选框左对齐&#xff0c;想要设置复选框居中对齐&#xff0c;不知道如何处理&#xff0c;这里给出代码与运行效果&a…

用Python画一条祥龙,祝您新年龙腾万里

用Python画一条祥龙&#xff0c;祝您新年龙腾万里 龙年到了&#xff0c;祝大家新年龙行龘龘&#xff0c;龙腾万里&#xff01; 从2021年开始&#xff0c;我每年都用Python画一幅当年生肖的图。 用Python标准库turtle画一头金牛&#xff0c;祝您新年牛气冲天&#xff01; 用P…

vscode 括号 python函数括号补全

解决方法 在setting.json中添加 “python.analysis.completeFunctionParens”: true 打开设置&#xff1b; 点击图中按钮打开setting.json文件 添加 “python.analysis.completeFunctionParens”: true

apipost 简单的性能压测总结

1、简单的使用机型牌评估 1&#xff09;jdk默认256M给100用&#xff0c;推荐给1000人同时用JVM 堆栈建议2G~4G&#xff08;目前定了机型4核8G内存 2T磁盘做radio0存储&#xff09;&#xff1b; 2&#xff09;数据库配置文件写了占了2G内存&#xff08;my.cnf文件&#xff09…

Python算法100例-1.3 牛顿迭代法求方程根

完整源代码项目地址&#xff0c;关注博主私信’源代码’后可获取 1&#xff0e;问题描述 编写用牛顿迭代法求方程根的函数。方程为 a x 3 b x 2 c x d 0 ax^3bx^2cxd0 ax3bx2cxd0&#xff0c;系数a、b、c、d由主函数输入&#xff0c;求x在1附近的一个实根。求出根后&…

详解SkyWalking前端监控的性能指标

SkyWalking 从8.2.0版本开始支持对前端浏览器端的性能进行监控&#xff0c;不仅可以像以前一样监控浏览器发送给后端服务的与请求&#xff0c;还能看到前端的渲染速度、错误日志等信息——这些信息是获取最终用户体验的最有效指标。实现的方式是引入skywalking-client-js库&…

STL篇三:list

文章目录 前言1.list的介绍和使用1.1 list的介绍1.2 list的使用1.3 list的迭代器的失效 2.list的模拟实现2.1 结点的封装2.2 迭代器的封装2.2.1 正向迭代器2.2.2 反向迭代器 2.3 list功能的实现2.3.1 迭代器的实例化及begin()、end() 2.3.2 构造函数2.3.3 赋值运算符重载2.3.4 …

YGG 为 Axie Infinity: Origins 发布超级任务游戏内训练器,深化对 Ronin 的支持

自 2023 年以来&#xff0c;Ronin 已成为增长最快的游戏区块链。由于 Axie Infinity 和 Pixels 等游戏的持续成功&#xff0c;日活跃用户数量至少增长了 10 倍。在过去的一年里&#xff0c;有超过 120 万个新地址加入&#xff0c;并且&#xff0c;这个数字还在持续增长。 ​Ron…

科技助力快乐养老,山东恒康养老服务中心与清雷科技达成合作

谈到养老服务&#xff0c;大家或许会有一些刻板印象。 如果说一个落落大方、笑容温柔的90后女孩是一家养老院的院长&#xff0c;很多人都会感到诧异。但就是这位来自山东省龙口市恒康养老服务中心的90后院长韩雨&#xff0c;实现了百分百入住率、百分百好评的养老服务奇迹。 韩…

北斗卫星在物联网时代的应用探索

北斗卫星在物联网时代的应用探索 在当今数字化时代&#xff0c;物联网的应用已经深入到人们的生活中的方方面面&#xff0c;让我们的生活更加智能便捷。而北斗卫星系统作为我国自主研发的卫星导航系统&#xff0c;正为物联网的发展提供了强有力的支撑和保障。本文将全面介绍北…

【软件设计师笔记】深入探究操作系统

【软件设计师笔记】计算机系统基础知识考点(传送门) &#x1f496; 【软件设计师笔记】程序语言设计考点(传送门) &#x1f496; &#x1f413; 操作系统的作用 1.通过资源管理提高计算机系统的效率 2.改善人机界面向用户提供友好的工作环境 &#x1f413; 操作系统的特征 …

nodejs 事件循环

浏览器的事件循环比较熟悉了&#xff0c;也来了解下 node 的。 参考来源&#xff1a; https://nodejs.org/en/guides/event-loop-timers-and-nexttick/ https://juejin.cn/post/6844903999506923528 事件循环分为 6 个阶段&#xff0c;图中每个框都是一个阶段&#xff0c;每个阶…

Acwing---827.双链表

双链表 1.题目2.基本思想3.代码实现 1.题目 实现一个双链表&#xff0c;链表初始为空&#xff0c;支持5种操作&#xff1a; 在最左侧插入一个数&#xff1b;在最右侧插入一个数&#xff1b;将第 k k k 个插入的数删除&#xff1b;在第 k k k个插入的数左侧插入一个数&#…

安装Canal

安装和配置Canal 下面我们就开启mysql的主从同步机制&#xff0c;让Canal来模拟salve 1.开启MySQL主从 Canal是基于MySQL的主从同步功能&#xff0c;因此必须先开启MySQL的主从功能才可以。 这里以之前用Docker运行的mysql为例&#xff1a; 1.1.开启binlog 打开mysql容器…

景联文科技受邀出席全国信标委生物特征识别分委会二届五次全会

全国信息技术标准化技术委员会生物特征识别分技术委员会&#xff08;SAC/TC28/SC37&#xff0c;以下简称“分委会”&#xff09;二届五次全会于2024年1月30日在北京顺利召开&#xff0c;会议由分委员秘书长王文峰主持。 分委会由国家标准化管理委员会批准成立&#xff0c;主要负…

社交平台内容创作未来会有哪些方向?

内容为王的时代下&#xff0c;企业如果想要通过社交平台占据用户心智&#xff0c;可以找到适合自己的内容营销策略&#xff0c;好的内容能够与消费者建立信任关系&#xff0c;今天 媒介盒子就来和大家聊聊&#xff1a;社交平台内容创作的方向。 一、 内容逐渐细分 相比于原来…

WorkPlus打造个性化移动门户,实现协作创新与工作高效

在移动办公逐渐成为企业工作方式的主流趋势下&#xff0c;构建高效的移动门户平台对于提升信息传递与团队协作效能至关重要。移动门户作为企业信息交流和协作的重要枢纽&#xff0c;WorkPlus以其领先的功能和卓越的性能&#xff0c;助力企业实现智能移动门户平台的搭建。 为何…

在WORD中设置公式居中编号右对齐设置方式

1 软件环境 Office Microsoft Office LTSC 专业增强版2021 2 最终效果 3 操作步骤 编辑公式&#xff1b;光标定位到公式的最后&#xff08;不是行的最后&#xff09;&#xff1b;输入#编号光标定位在公式最后&#xff08;不是行的最后&#xff09;&#xff0c;按Enter键回车…

R3 下动态加载的模块的保护(一)

前言 在 R3 下防护动态加载的模块不被意外卸载需要很多的策略&#xff0c;比如&#xff1a;LDR 断链、VAD 记录擦除、PE 头擦除、修改入口函数、内存注入等。文本我们将浅析模块静态化技术这一项技术。模块静态化是一个很常见的模块保护技术&#xff0c;它通过修改模块的引用计…