跳表的实现

news2024/9/22 1:42:42

目录

    • 简介
    • 跳表的实现

简介

skiplist本质也是一种查找结构,和搜索树、哈希表一样可以作为key或者key/value模型的查找结构,从命名可以看出它也是一个链表结构,链表的查找效率是O(n),作为在链表基础上优化的一种查找结构,跳表的查找的平均时间复杂度是O(logn),跳表是在有序链表的基础上发展起来的。优化的思路如下:

  1. 假如我们每相邻两个节点升高一层,增加一个指针,让指针指向下下个节点,如下图b所示。这 样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半。由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了,需要比较的节点数大概只有原来的一半。

  2. 以此类推,我们可以在第二层新产生的链表上,继续为每相邻的两个节点升高一层,增加一
    个指针,从而产生第三层链表。如下图c,这样搜索效率就进一步提高了。

  3. skiplist正是受这种多层链表的想法的启发而设计出来的。实际上,按照上面生成链表的方
    式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似
    二分查找,使得查找的时间复杂度可以降低到O(log n)。但是这个结构在插入删除数据的时
    候有很大的问题,插入或者删除一个节点之后,就会打乱上下相邻两层链表上节点个数严格
    的2:1的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也
    包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成O(n)。

在这里插入图片描述

  1. skiplist的设计为了避免这种问题,做了一个大胆的处理,不再严格要求对应比例关系,而是
    插入一个节点的时候随机出一个层数。这样每次插入和删除都不需要考虑其他节点的层数,
    这样就好处理多了。细节过程入下图:

在这里插入图片描述
插入一个节点,这个节点随机一个层数的方式受两个参数的影响,p增加一层的概率,maxlenvel最大层数限制,伪代码如下
在这里插入图片描述
C++实现如下

int randomlevel()
	{
		int level = 1;
		while (rand() <= RAND_MAX * p && level < maxlevel)  //RAND_MAX是能随机生成的最大值
			++level;

		return level;
	}

Redis的跳表实现中p是取1/4 maxlevel取32。
根据伪代码可以看出,产生越高的节点层数概率越低
节点层数等于1的概率为1-p
层数大于等于2的概率为p,恰好等于2的概率为p(1-p)
层数大于等于3的概率为p2,恰好等于3的概率为p2(1-p)
层数大于等于4的概率为p3, 恰好等于4的概率为p3(1-p)

一个节点的平均层数计算公式为

在这里插入图片描述
当p=1/2时,每个节点包含的平均指针数目为2
当p=1/4时,每个节点包含的平均指针数目为1.33

跳表的实现

struct skiplistnode
{
	int _val;
	vector<skiplistnode*>_nextV;

	skiplistnode(int val,int level)
		:_val(val)
		,_nextV(level,nullptr)
	{}
};
class Skiplist
{
	typedef skiplistnode node;
public:
	Skiplist()
	{
		_head = new node(-1, 1);
		srand((unsigned int)time(0));
	}
	bool search(int target)
	{
		node* cur = _head;
		int level = _head->_nextV.size()-1;
		while (level>=0)
		{
			if (cur->_nextV[level] && cur->_nextV[level]->_val < target)
			{
				cur = cur->_nextV[level];
			}
			else if (cur->_nextV[level] == nullptr || cur->_nextV[level]->_val > target)
			{
				--level;
			}
			else
				return true;	
		}
		return false;
	}
	vector<node*> findprevnode(int num)
	{
		node* cur = _head;
		int level = _head->_nextV.size() - 1;
		vector<node*>prev(level + 1, _head);
		while (level >= 0)
		{
			if (cur->_nextV[level] && cur->_nextV[level]->_val < num)
			{
				cur = cur->_nextV[level];
			}
			else if (cur->_nextV[level] == nullptr || cur->_nextV[level]->_val >= num)
			{
				prev[level] = cur;
				--level;
			}
		}
		return prev;
	}
	void add(int num)
	{
		vector<node*>prev = findprevnode(num);
		int n = randomlevel();
		node* newnode = new node(num, n);
		if (n > prev.size())
		{
			prev.resize(n, _head);
			_head->_nextV.resize(n);
		}
		for (int i = 0; i < n; ++i)
		{
			newnode->_nextV[i] = prev[i]->_nextV[i];
			prev[i]->_nextV[i] = newnode;
		}
	}
	bool erase(int num)
	{
		vector<node*>prev = findprevnode(num);
		if (prev[0]->_nextV[0] == nullptr || prev[0]->_nextV[0]->_val != num) //说明不存在num
			return false;
		else
		{
			node* del = prev[0]->_nextV[0];
			for (int i = 0; i < del->_nextV.size(); ++i)
			{
				prev[i]->_nextV[i] = del->_nextV[i];
			}
			delete del;
			int level = _head->_nextV.size() - 1;
			while (level>0&&_head->_nextV[level] == nullptr)
			{
				--level;
			}
			_head->_nextV.resize(level + 1); //删除一个节点后可能导致最顶上有些层数指针指向空,把这些没意义的层数减一下来
			return true;
		}
	}
	int randomlevel()
	{
		int level = 1;
		while (rand() <= RAND_MAX * p && level < maxlevel)
			++level;

		return level;
	}
	void print()
	{
		node* cur = _head;
		while (cur)
		{
			printf("%2d\n", cur->_val);
			for (auto& e : cur->_nextV)
			{
				printf("%2s", "↓");
			}
			printf("\n");
			cur = cur->_nextV[0];
		}
	}
private:

	node* _head;
	double p = 0.4;
	int maxlevel = 32;
};

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

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

相关文章

app拉新充场代理

我认为您可能是想了解APP充值拉新软件的功能吧。通常&#xff0c;APP充值拉新软件会具有以下功能&#xff1a; 充值服务&#xff1a;提供多种支付方式&#xff0c;让用户方便快捷地进行充值操作。 活动推广&#xff1a;通过不同的方式&#xff0c;如折扣、优惠码等&…

Tomcat的部署和优化

Tomcat的组件构成 &#xff08;1&#xff09;Web 容器&#xff1a;完成 Web 服务器的功能。 &#xff08;2&#xff09;Servlet 容器&#xff1a;名字为 catalina&#xff0c;用于处理 Servlet 代码。 &#xff08;3&#xff09;JSP 容器&#xff1a;用于将 JSP 动态网页翻译成…

centos系统安装mysql8.0

centos系统安装mysql8.0 环境说明开始1、查看centos7中是否有MariaDB&#xff0c;MariaDB与MySQL关系请自行查阅2、如果有MariaDB&#xff0c;需要将 步骤1 中查询到的mairadb全部卸载&#xff0c;否则MySQL安装会出现问题3、查看本机是否已经安装过MySQL4、如果安装过MySQL&am…

9.java程序员必知必会类库之加密库

前言 密码学在计算机领域源远流长&#xff0c;应用广泛。当前每时每刻&#xff0c;每一个连接到互联网的终端&#xff0c;手机&#xff0c;电脑&#xff0c;iPad都会和互联网有无数次的数据交互&#xff0c;如果这些数据都是明文传输那将是难以想象的。为了保护用户隐私&#…

算法--前缀和技巧 (蓝桥杯123-灵能传输)

文章目录 什么是前缀和用途什么时候用例题[蓝桥杯 2021 国 ABC] 123题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示 思路代码 灵能传输(蓝桥杯96%&#xff0c;洛谷ac)[蓝桥杯 2019 省 B] 灵能传输题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1…

知识图谱实战开篇-讲述知识图谱是什么,要学哪些知识,一文讲通

大家好&#xff0c;我是微学AI&#xff0c;今天给大家带来知识图谱重要讲述&#xff0c;讲明白什么是知识图谱&#xff0c;知识图谱可以做什么&#xff0c;需要学哪些知识&#xff0c;与自然语言处理的关系。很多人认为知识图谱是关系图谱&#xff0c;可能涉及人工智能的东西不…

【LeetCode】650. 只有两个键的键盘

650. 只有两个键的键盘&#xff08;中等&#xff09; 思路 不同于以往通过加减实现的动态规划&#xff0c;这里需要乘除法计算位置。因为粘贴操作是倍数增加&#xff0c;使一个一维数组 dp&#xff0c;其中位置 i 表示延展到长度 i 的最少操作次数。对于每个位置 j &#xff0c…

C++学习 Day6

目录 1. 类对象模型 1.1 如何计算类对象的大小 1.2 类对象的存储方式 1.3 结构体内存对齐规则 2. this指针 2.1 this指针的引出 2.2 this指针的特性 3. 类的6个默认成员函数 4.构造函数 4.1 概念 4.2 特性 1. 类对象模型 1.1 如何计算类对象的大小 class A { publi…

【Java】『蓝桥杯』10道编程题及答案(一)

系列文章 【Java】『蓝桥杯』10道编程题及答案&#xff08;一&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/130223115 【Java】『蓝桥杯』10道编程题及答案&#xff08;二&#xff09; 本文链接&#xff1a;https://blog.csdn.net/y…

【Hello Network】协议

作者&#xff1a;小萌新 专栏&#xff1a;网络 作者简介&#xff1a;大二学生 希望能和大家一起进步 本篇博客简介&#xff1a;简单介绍下协议并且设计一个简单的网络服务器 协议 协议的概念结构化数据传输序列化和反序列化网络版计算机服务端代码协议定制客户端代码服务线程执…

[Netty] HashWheelTimer时间轮 (十六)

文章目录 1.常见定时任务实现2.时间轮算法3.HashedWheelTimer源码分析3.1 内部结构分析3.2 构造方法3.3 添加任务3.4 工作线程Worker3.5 停止时间轮 4.HashWheelTimer总结 1.常见定时任务实现 定时器的使用场景包括&#xff1a;成月统计报表、财务对账、会员积分结算、邮件推送…

Linux Podman容器介绍

目录 Podman讲解 Container 和 Container Images 的关系 安装Podman 配置root的容器管理 国内镜像源 配置Podman的镜像源 创建容器相关命令 配置rootless的容器管理 配置Podman镜像源 管理容器镜像 管理容器 将容器作为systemd服务运行 配置普通用户来创建systemd…

Jetpack Compose之线性布局和帧布局

概述 Compose 中的线性布局对应的是Android传统视图中的LinearLayout,不一样的地方是&#xff0c;Compose根据Orientation的不同又将布局分为Column和Row, Column对应传统视图LinearLayout中orientation “vertical”的情况&#xff0c;Row对应传统视图LinearLayout中orienta…

Redis入门学习笔记【二】Redis缓存

目录 一、Redis缓存 二、Redis使用缓存遇到的问题 2.1 数据一致性 2.2缓存雪崩 2.3 缓存穿透 2.4 缓存击穿 一、Redis缓存 数据缓存是Redis最重要的一个场景&#xff0c;为缓存而生&#xff0c;在springboot中&#xff0c;一般有两种使用方式&#xff1a; 直接通过RedisT…

helm部署相关服务过程中问题记录

在学习helm部署相关服务过程中出现一些相关问题&#xff0c;自己记录并供大家一起学习&#xff01;&#xff01;&#xff01; 【问题1】部署helm 获取软件包失败 在通过wget https://storage.googleapis.com/kubernetes-helm/helm-v2.13.1-linux-amd64.tar.gz文件过程发现无法…

消息中间件的定义

中间件(middleware)是基础软件的一大类&#xff0c;属于可复用的软件范畴。中间件在操作系统软件&#xff0c;网络和数据库之上&#xff0c;应用软件之下&#xff0c;总的作用是为处于自己上层的应用软件提供运行于开发的环境&#xff0c;帮助用户灵活、高效的开发和集成复杂的…

【软考数据库】第二章 程序语言基础知识

目录 2.1 程序设计语言的基本概念2.2 程序设计语言的基本成分2.3 编译程序基本原理 前言&#xff1a; 笔记来自《文老师软考数据库》教材精讲&#xff0c;精讲视频在b站&#xff0c;某宝都可以找到&#xff0c;个人感觉通俗易懂。 2.1 程序设计语言的基本概念 程序设计语言是…

Nginx中的location规则与rewrite重写

location与rewrite的区别 rewrite &#xff1a;对访问的域名或者域名内的URL路径地址重写 location&#xff1a;对访问的路径做访问控制或者代理转发 从功能看 rewrite 和 location 似乎有点像&#xff0c;都能实现跳转&#xff0c;主要区别在于 rewrite 是在同一域名内更改获…

常见的3d bounding box标注工具

0. 简介 对于3d bounding box而言&#xff0c;近几年随着自动驾驶的火热&#xff0c;其标注工具也日渐多了起来&#xff0c;本篇文章不讲具体的算法&#xff0c;这里主要聚焦于这些开源的3d bounding box标注工具&#xff0c;以及他们是怎么使用的。这里借鉴了我想静静&#x…

牛客前端编程语言错题2

【语法】 名为“ctx”的变量是某个HTML5画布对象的上下文。以下代码绘制的是什么&#xff08;&#xff09; Ctx.arc(x,y,r,0,Math.PI,true); 在给定点绘制一个矩形 从一个点到另一个点绘制一条直线 在给定点绘制一个半圆 在给定点绘制一个圆 链接&#xff1a;https://www.now…