[数据结构] 深入理解什么是跳表及其模拟实现

news2024/9/30 21:30:44

跳表

  • 定义
  • 优化
  • 实现基本框架
    • 定义跳表结点
    • 实现基础结构
    • 构造函数
  • 实现基本操作
    • 查找操作
    • 插入数据
    • 删除某结点
    • 打印跳表
  • 跳表与平衡搜索树和哈希表的对比

定义

跳表

  • 每相邻两个节点升高一层,增加一个指针,让指针指向下下个节点;上面每一层链表的节点个数,是下面一层的节点个数的一半,这样在查找的时候就类似于二分查找。

优化

跳表

  • 如上图所示,插入或删除的时候会破坏跳表中这种2:1的对应关系
  • 解决方法:不再要求严格的比例关系,把结点插入到相应位置之后,再给其随机出一个层数,保证每个结点的插入和删除操作和其他结点没关系,都是独立的,不需要调整其他结点的关系;
    一般跳表会设计一个最大层数maxLevel的限制和一个多增加一层的概率p;
    当p=1/2时,每个节点所包含的平均指针数目为2;
    当p=1/4时,每个节点所包含的平均指针数目为1.33。

实现基本框架

定义跳表结点

  • 每个跳表结点内部包含值域指针数组域
template <class T>
struct SkiplistNode{
	T _data;
	vector<SkiplistNode*> _nextv;

	SkiplistNode(T data, int level)
		:_data(data),
		_nextv(level, nullptr){
	}
};

实现基础结构

template <class T>
class Skiplist{
public:
	typedef SkiplistNode<T> Node;
private:
	Node* _head;  //头结点
	size_t _maxlevel = 10;  //跳表的最大层数
	double _p = 0.25;  //多增加一层的概率
};

构造函数

  • 跳表初始状态下,仅有头结点,其层数为1,值为-1;
Skiplist() {
	srand(time(0));  //随机数种子
	_head = new Node(-1, 1);  //初始时,头结点为1层
}

实现基本操作

查找操作

  • 从头结点开始,按从上层到下层的数据进行查找
  • target大于当前层下一个不为空的结点,向右走
  • target小于当前层下一个结点或下一个结点为空,向下走
	bool search(int target) {
		size_t level = _head->_nextv.size()-1;  //level表示层数的下标
		Node* cur = _head;
		while (level >= 0){
			if (cur->_nextv[level] != nullptr && target > cur->_nextv[level]->_data){
				//target大于当前层下一个不为空的结点,向右走
				cur = cur->_nextv[level];
			}
			else if (cur->_nextv[level] == nullptr || target < cur->_nextv[level]->_data){
				//target小于当前层下一个结点或下一个结点为空,向下走
				level--;
			}
			else
				return true;
		}
		return false;
	}

插入数据

  • 先找待插入数据的位置,并且保留待插入位置的每一层的前一个结点
  • 前一个结点:若需要向下查找,则当前结点为待插入结点当前层的前一个结点
vector<Node*> findPrevV(int num){
		int level = _head->_nextv.size() - 1;
		Node* cur = _head;

		vector<Node*> prevV(level + 1, _head); //初始其每层前一个结点都为_head

		while (level >= 0){
			if (cur->_nextv[level] != nullptr && num > cur->_nextv[level]->_data){
				cur = cur->_nextv[level];
			}
			else if (cur->_nextv[level] == nullptr || num <= cur->_nextv[level]->_data){
				//向下走,当前结点为待插入结点当前层的前一个结点
				prevV[level] = cur;
				--level;
			}
		}
		return prevV;
	} 
  • 插入结点,并从下到上连接该结点到跳表中
void add(int num) {
		//1.记录待插入结点每一层的前一个结点
		vector<Node*> prevV = findPrevV(num);

		//2.插入结点
		int n = randomLevel();
		Node* newnode = new Node(num, n);
		//若待插入结点的层数大于头结点的层数,升高_head及prevV数组的层数
		if (n > _head->_nextv.size()){
			_head->_nextv.resize(n, nullptr);
			prevV.resize(n, _head);  //高出层数的前一个结点都为_head
		}
		//从下到上连接新结点的前后结点到跳表中
		for (size_t i = 0; i < n; i++){
			newnode->_nextv[i] = prevV[i]->_nextv[i];
			prevV[i]->_nextv[i] = newnode;
		}
	}

删除某结点

  • 先找待删除结点的位置,并且保存待删除结点的每一层的前一个结点
  • 若待删除结点的第0层的前一个结点的下一个结点为空或其值不为num,说明num结点不在跳表中
  • 删除num结点,自底向上对各层进行连接
bool erase(int num) {
		//1.先找到待删除结点每一层的前一个结点
		vector<Node*> prevV = findPrevV(num);
		
		//若num结点的第0层的前一个结点的下一个结点为空或其值不为num
		//说明num结点不在跳表中
		if (prevV[0]->_nextv[0] == nullptr || prevV[0]->_nextv[0]->_data != num){
			return false;
		}
		//2.删除num结点
		else{
			Node* delnode = prevV[0]->_nextv[0];
			for (int i = 0; i < delnode->_nextv.size(); i++){
				prevV[i]->_nextv[i] = delnode->_nextv[i];
			}
			delete delnode;
			return true;
		}
	}

打印跳表

  • 从上到下,按层访问结点
void print(){
		int level = _head->_nextv.size() - 1;
		for (int i = level; i >= 0; i--){
			Node* cur = _head->_nextv[i];
			while (cur){
				cout << cur->_data << " ";
				cur = cur->_nextv[i];
			}
			cout << endl;
		}
	}

跳表与平衡搜索树和哈希表的对比

  1. skiplist相比平衡搜索树(AVL树和红黑树)的优势:
    a、skiplist实现简单,容易控制
    b、skiplist的额外空间消耗更低:平衡树节点存储每个值有三叉链,平衡因子/颜色等消耗;skiplist中p=1/2时,每个节点所包含的平均指针数目为2;skiplist中p=1/4时,每个节点所包含的平均指针数目为1.33。
  2. skiplist相比哈希表而言:
    缺点:哈希表查找的平均时间复杂度是O(1),比skiplist快
    优点:a、遍历数据有序;
    b、消耗略小一点,哈希表存在链接指针和表空间消耗;
    c、哈希表扩容有性能损耗;
    d、哈希表在极端场景下哈希冲突高,效率下降厉害,需要红黑树补足接力。

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

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

相关文章

Xshell 7 连接云服务器的步骤和出现的错误

一、工具准备云服务器Xshell 7二、使用 Xshell 7 连接数据库三、新建会话属性后&#xff0c;没有自动弹出 SSH 用户名要求输入四、SSH 用户身份验证不能输入 Password五、Xshell 连接 centos 7 服务器 报错提示 “ssh服务拒绝了密码&#xff0c;请再试一次“&#xff0c;但是密…

信息安全工程

信息安全工程信息安全工程信息安全工程概述信息安全工程理论基础支撑信息安全工程的理论基础质量管理基本概念信息安全工程原理ISSE活动中支持认证和认可的活动信息安全工程监理模型信息安全工程能力评估SSE-CMM&#xff08;系统安全工程能力成熟度模型&#xff09;SSE-CMM 的安…

已解决SyntaxError: EOL while scanning string literal

已解决SyntaxError: EOL while scanning string literal 文章目录报错问题报错翻译报错原因解决方法联系博主免费帮忙解决报错报错问题 粉丝群里面的一个小伙伴遇到问题跑来私信我&#xff0c;想用eval函数转换字符串类型的字典&#xff0c;但是发生了报错&#xff08;当时他心…

全网最牛最全面的自动化平台从0到1地一步步搭建

来到新的公司有半年多了&#xff0c;由于业务和人员的极速扩张&#xff0c;整个局面处于百废待兴阶段&#xff0c;有太多方方面面的事情要做&#xff0c;前五个月基本上都是在给各式各样的需求进行支援&#xff0c;最近的两个月多月才比较固定做技术域的事情。所在组主要是做一…

前端学习第一阶段:第六章 HTML和CSS3

6-1 HTML5 01-HTML5CSS3提高导读 02-HTML5提高-新增语义化标签 03-HTML5-新增视频标签 04-HTML5新增音频标签 05-HTML5新增input表单 06-HTML5新增表单属性 6-2 CSS3 07-CSS3新增属性选择器&#xff08;上&#xff09; 08-CSS3新增属性选择器&#xff08;下&#xff09; 09-CSS…

C语言(指针,数组和函数)

目录 一.指针和数组 1.指向数组 2.数组下标和指针解引用的相等性 3.*和优先级问题 二..函数&#xff0c;数组和指针 1.声明数组形参 2.使用const保护地址数据 3.指针和多维数组 4.指向多维数组的指针 5.函数和多维数组 一.指针和数组 在前面的章节里面我们已经说过了…

Mr. Cappuccino的第39杯咖啡——Kubernetes之深入理解Pod

Kubernetes之深入理解PodPod相关概念Pod详细配置清单Pod核心配置Pod基本配置1. 创建yaml文件2. 创建namespace并根据yaml文件创建资源3. 查看namespace下的pod列表以及pod的详细信息Pod中多个容器的名称和端口号不能相同Pod镜像拉取策略Pod环境变量Pod端口相关设置Pod资源相关配…

echarts修改饼图,环形图的圆环宽度,大小

echarts修改环形图的圆环宽度&#xff0c;大小 环形图圆环的大小需要通过series-pie. radius属性来修改 radius 饼图的半径。 Array.<number|string>&#xff1a;数组的第一项是内半径&#xff0c;第二项是外半径。每一项遵从上述 number string 的描述。 把数组的第…

前端高频面试题—JavaScript篇(四)

&#x1f4bb; 前端高频面试题—JavaScript篇&#xff08;四&#xff09;&#x1f3e0;专栏&#xff1a;前端面试题 &#x1f440;个人主页&#xff1a;繁星学编程&#x1f341; &#x1f9d1;个人简介&#xff1a;一个不断提高自我的平凡人&#x1f680; &#x1f50a;分享方向…

150家半导体企业IPO最新进展(附企业名录)

前言 根据Omdia的数据显示&#xff0c;2022年全球在第一季度、第二季度、第三季度实现的半导体收入分别为1593亿美元、1581亿美元、1470亿美元&#xff0c;分别环比下降0.03%、1.9%、7.0%。 目前&#xff0c;半导体产业链经历了自2022上半年的欣欣向荣&#xff0c;到2022年下半…

万字长文掌握Python高并发

文章目录0 前言1 并发、并行、同步、异步、阻塞、非阻塞1.1 并发1.2 并行1.3 同步1.4 异步1.5 阻塞1.6 非阻塞2 多线程2.1 Python线程的创建方式2.1.1 方式一2.1.2 方式二 继承Thread2.1.3 通过线程池创建多线程2.2 聊聊GIL2.2.1 Python线程与操作系统线程的关系2.3 线程同步2.…

【CICD】Jenkins 部署 Docker 容器形态的后端服务

在实现 Jenkins 构建部署前端项目之后&#xff0c;逐渐对使用 Jenkins 部署后端服务有了一定兴趣&#xff1b;总体流程没有什么很大的变化&#xff0c;不过是后端服务需要以 Docker 的形式进行启动&#xff0c;在此记录一下具体过程&#xff08;部分过程与构建部署前端相同不做…

windows下载安装jdk1.8(jdk8)基础篇

一、前言 目前jdk最高升级到JDK19版本了&#xff0c;但是大部分应用系统都是用的1.8&#xff0c;对于初学者来说&#xff0c;也需要下载安装这个版本的jdk。 二、下载安装步骤 一、我已经下载下来&#xff0c;大家到【我的下载目录】下载&#xff0c;密码3360&#xff0c;分…

使用Benchto框架对Trino进行SQL性能对比测试

有时需要对魔改源码前后的不同版本Trino引擎进行性能对比测试&#xff0c;提前发现改造前后是否有性能变差或变好的现象&#xff0c;避免影响数据业务的日常查询任务性能。而Trino社区正好提供了一个性能测试对比框架&#xff1a;GitHub - trinodb/benchto: Framework for runn…

金额大写转换

金额大写转换&#xff08;C语言 &#xff09; 本人喜欢探索各种算法。见站内好多此类文章&#xff0c;有些很好&#xff0c;有些不完整。姑且也来凑下热闹。 金额大写应用在很多方面&#xff0c;如支票、发票、各种单据&#xff0c;各种财务凭证&#xff0c;合同文本金额部分。…

【逐步剖C】-第七章-数据的存储

一、数据类型介绍 1. C语言基本内置类型&#xff1a; char //字符数据类型 short //短整型 int //整形 long //长整型 long long //更长的整形 float //单精度浮点数 double //双精度浮点数2. 类型的基本归类 &#xff08;1&#xff09;整型&#xff1a; charunsign…

c语言指针

指针 指针是存放地址的变量&#xff0c;也可以说指针地址。 对于定义p&#xff08;这里的话&#xff0c;只是定义&#xff0c;说明p是指针&#xff09;&#xff0c;p作为一个指针去指向存放数据的位置&#xff0c;而p意思是取&#xff08;p指向的内存位置的数据&#xff09;&…

es启动,浏览器无法访问9200

通过brew成功启动es&#xff0c;但是访问http://localhost:9200/报错&#xff0c;连接被拒绝 %:brew services start elasticsearch-full> Successfully started elasticsearch-full (label: homebrew.mxcl.elasticsearc可能原因如下&#xff1a; 1、安装java 要先安装ja…

聊聊async/await原理

前言 我们知道Promise的出现极大地解决了回调地狱&#xff0c;但是如果使用流程非常复杂的话&#xff0c;就非常容易过多地调用Promise的then()方法&#xff0c;这样也不利于使用和阅读。 例如&#xff1a;我希望在请求 www.baidu.com 后输出请求的结果&#xff0c;再去请求 …

【基于腾讯云的远程机械臂小车】

1. 项目来源 项目源码地址&#xff1a;https://gitcode.net/VOR234/robot_arm_car/-/blob/master/TencentOS-tiny123.zip https://gitee.com/vor2345/robot_arm_car 程序分别 视频演示&#xff1a;https://www.bilibili.com/video/BV15M4y1D7MD/?vd_source530bf85167de80ff…