当前位置: 首页 > news >正文

[C++][数据结构][跳表]详细讲解

目录

  • 0.什么是跳表?
  • 1.SkipList的优化思路
  • 2.SkipList的效率如何保证?
  • 3.SkipList实现
  • 4.SkipList VS 平衡搜索树 && Hash


0.什么是跳表?

  • SkipList本质上也是一种查找结构,用于解决算法中的查找问题,跟平衡搜索树和哈希表的价值是一样的,可以作为key或者key/value的查找模型
  • SkipList,是在有序链表的基础上发展起来的
    • 如果是一个有序的链表,查找数据的时间复杂度是 O ( N ) O(N) O(N)

1.SkipList的优化思路

  • 假如每相邻两个节点升高一层,增加一个指针,让指针指向下下个节点,如下图b所示

    • 这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半
    • 由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了,需要比较的节点数大概只有原来的一半
  • 以此类推,可以在第二层新产生的链表上,继续为每相邻的两个节点升高一层,增加一个指针,从而产生第三层链表,这样搜索效率就进一步提高了,如下图c

  • SkipList正是受这种多层链表的想法的启发而设计出来的

  • 实际上,按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似二分查找,使得查找的时间复杂度可以降低到 O ( l o g 2 N ) O(log_2N) O(log2N)

  • 但是这个结构在插入删除数据的时候有很大的问题

    • 插入或者删除一个节点之后,就会打乱上下相邻两层链表上节点个数严格的2:1的对应关系

    • 如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成O(n)

      请添加图片描述

  • SkipList的设计为了避免这种问题,做了一个大胆的处理

    • 不再严格要求对应比例关系,而是插入一个节点的时候随机出一个层数

    • 这样每次插入和删除都不需要考虑其他节点的层数, 这样就好处理多了

      请添加图片描述


2.SkipList的效率如何保证?

  • SkipList插入一个节点时随机出一个层数,听起来这么随意,如何保证搜索时的效率呢?

  • 首先要细节分析的是这个随机层数是怎么来的

    • 一般跳表会设计一个最大层数maxLevel的限制
    • 其次会设置一个多增加一层的概率p
    • 计算这个随机层数的伪代码如下图:
      请添加图片描述
  • **参考:**在Redis的SkipList实现中,这两个参数的取值为:

    p = 1/4;
    maxLevel = 32;
    
  • 根据前面randomLevel()的伪代码,产生越高的节点层数,概率越低

    • 节点层数至少为1,而大于1的节点层数,满足一个概率分布
    • 节点层数恰好等于1的概率为 1 − p 1-p 1p
    • 节点层数大于等于2的概率为 p p p,而节点层数恰好等于2的概率为 p ∗ ( 1 − p ) p*(1-p) p(1p)
    • 节点层数大于等于3的概率为 p 2 p^2 p2,而节点层数恰好等于3的概率为 p 2 ∗ ( 1 − p ) p^2*(1-p) p2(1p)
    • 节点层数大于等于4的概率为 p 3 p^3 p3,而节点层数恰好等于4的概率为 p 3 ∗ ( 1 − p ) p^3*(1-p) p3(1p)
  • 因此,一个节点的平均层数(即包含的平均指针数目),计算如下:

    • p = 1 / 2 p=1/2 p=1/2时,每个节点所包含的平均指针数目为2
    • p = 1 / 4 p=1/4 p=1/4时,每个节点所包含的平均指针数目为1.33
      请添加图片描述
  • SkipList的平均时间复杂度为: O ( l o g N ) O(logN) O(logN)


3.SkipList实现

  • 插入结点的关键是找到这个位置的每一层前一个结点
    • 比它小,向下走
    • 比它大,向右走
struct SkipListNode
{int _val;vector<SkipListNode*> _nextV;SkipListNode(int val, int level): _val(val), _nextV(level, nullptr){}
};class Skiplist
{typedef SkipListNode Node;
public:Skiplist(){srand(time(nullptr));_head = new Node(-1, 1); // 头节点,层数是1}bool Search(int target){Node* cur = _head;int level = _head->_nextV.size() - 1;while (level >= 0){if (cur->_nextV[level] && target > cur->_nextV[level]->_val){// 目标值比下一个结点值大,向右走cur = cur->_nextV[level];}else if (!cur->_nextV[level] || target < cur->_nextV[level]->_val){// 下一个结点是空(尾) || 目标值比下一个节点值要小,向下走level--;}else{return true;}}return false;}void Add(int num){vector<Node*> preV = FindPrevNode(num);int n = RandomLevel();Node* newnode = new Node(num, n);// 如果n超过当前最大的层数,那就升高一下_head的层数if (n > _head->_nextV.size()){_head->_nextV.resize(n, nullptr);preV.resize(n, _head);}// 链接前后结点for (size_t 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);// 第一层下一个不是val,则val不在表中if (!preV[0]->_nextV[0] || preV[0]->_nextV[0]->_val != num){return false;}Node* del = preV[0]->_nextV[0];// del结点每一层前后指针链接起来for (size_t i = 0; i < del->_nextV.size(); i++){preV[i]->_nextV[i] = del->_nextV[i];}delete del;// 如果删除最高层结点,把头节点的层数也降一下// 可以稍微提高查找效率int i = _head->_nextV.size() - 1;while (i >= 0){if (!_head->_nextV[i]){i--;}else{break;}}_head->_nextV.resize(i + 1);return true;}// SkipList精髓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] && num > cur->_nextV[level]->_val){// 目标值比下一个结点值大,向右走cur = cur->_nextV[level];}else if (!cur->_nextV[level] || num <= cur->_nextV[level]->_val){preV[level--] = cur;}}return preV;}// v1.0 Cint RandomLevel(){size_t level = 1;// rand() / RAND_MAX -> [0, 1]while (rand() <= RAND_MAX * _p && level <= _maxLevel){level++;}return level;}// v2.0 C++// int RandomLevel()// {//     static std::default_random_engine generator(std::chrono::system_clock::now().time_since_epoch().count());// 	static std::uniform_real_distribution<double> distribution(0.0, 1.0);// 	size_t level = 1;// 	while (distribution(generator) <= _p && level < _maxLevel)// 	{// 		++level;// 	}// 	return level;// }
private:Node* _head;size_t _maxLevel = 32;double _p = 0.5;
};

4.SkipList VS 平衡搜索树 && Hash

  • SkipList相比平衡搜索树(AVL树和红黑树),都可以做到遍历数据有序,时间复杂度也差不多
    • SkipList的优势:
      • SkipList实现简单,容易控制
        • 平衡树增删查改遍历都更复杂
      • SkipList的额外空间消耗更低
        • 平衡树节点存储每个值有三叉链,平衡因子/颜色等消耗
        • SkipList中 p = 1 / 2 p=1/2 p=1/2时,每个节点所包含的平均指针数目为2
        • SkipList中 p = 1 / 4 p=1/4 p=1/4时,每个节点所包含的平均指针数目为1.33
  • SkipList相比哈希表而言,就没有那么大的优势了
    • SkipList劣势:
      • 哈希表平均时间复杂度是 O(1),比SkipList快
      • 哈希表空间消耗略多一点
    • SkipList优势:
      • 遍历数据有序
      • SkipList空间消耗略小一点,哈希表存在链接指针和表空间消耗
      • 哈希表扩容有性能损耗
      • 哈希表在极端场景下哈希冲突高,效率下降厉害,需要红黑树补足接力

相关文章:

[C++][数据结构][跳表]详细讲解

目录 0.什么是跳表&#xff1f;1.SkipList的优化思路2.SkipList的效率如何保证&#xff1f;3.SkipList实现4.SkipList VS 平衡搜索树 && Hash 0.什么是跳表&#xff1f; SkipList本质上也是一种查找结构&#xff0c;用于解决算法中的查找问题&#xff0c;跟平衡搜索树…...

tinyxml

github下载相关的软件包&#xff0c;其中有四个文件需要主要需要关注就是分别是tinyxml12.cpp&#xff0c;tinyxml12.h&#xff0c;rss网页xml文件&#xff0c;还有就是官方给的test文件tinyxmltest.cpp。 example1就是提供一个打开文件的方式 int example_1() {XMLDocument …...

Docker(三)-Docker常用命令

1.run run命令执行流程:2.帮助启动类命令 2.1 启动docker systemctl start docker2.2 停止docker systemctl stop docker2.3 重启docker systemctl restart docker2.4查看docker状态 systemctl status docker2.5开机启动 systemctl enable docker2.6查看docker概要信息 …...

[MRCTF2020]PixelShooter

一个apk文件 jeb打开发现是apk文件 apk游戏逆向必须知道的知识: 一般关键数据在 Assets/bin/data/managed/assembly-csharp.dll这个文件里面 我不知道jeb为什么这里我没有 apk是个压缩包 直接解压 这个文件解压也可以发现flag {Unity_1S_Fun_233}...

vue实现的商品列表网页

一、商品列表效果如下 二、代码&#xff1b; vue实现的商品列表网页 &#xff0c; 图片在vue项目的Public文件夹里的 imgs中 <template><div class"common-layout"><!-- el-container:外层容器。 当子元素中包含 <el-header> 或 <el-foo…...

【泛微系统】e-cology非标配功能概览

关于泛微非标功能的功能编号、功能名称及支持版本 编号名称支持版本001考勤功能4.500.0124-9.00+KB900190206002短信通用接口5.000.0327+KB50001003 及以上版本004计划任务接口5.0+KB50001003及以上版本005集成登录接口6.0及以上版本006流程中自定义浏览框5.0+KB50001003及以上…...

Python基础教程(二十八):pip模块

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…...

通信系统概述

1.定义 通信系统&#xff08;也称为通信网络&#xff09;是利用各种通信线路将地理上分散的、具有独立功能的计算机系统和通信设备按不同的形式连接起来&#xff0c;依靠网络软件及通信协议实现资源共享和信息传递的系统。 2.概述 随着通信技术和网络技术的不断发展&#xff…...

http发展史(http0.9、http1.0、http1.1、http/2、http/3)详解

文章目录 HTTP/0.9HTTP/1.0HTTP/1.1队头阻塞&#xff08;Head-of-Line Blocking&#xff09;1. TCP 层的队头阻塞2. HTTP/1.1 的队头阻塞 HTTP/2HTTP/3 HTTP/0.9 发布时间&#xff1a;1991年 特点&#xff1a; 只支持 GET 方法没有 HTTP 头部响应中只有 HTML 内容&#xff0…...

Hadoop 面试题(四)

1. 简述Hadoop节点的动态上线下线的大概操作 &#xff1f; 在Hadoop集群中&#xff0c;节点的动态上下线指的是在不停止整个集群服务的情况下&#xff0c;添加或移除节点。这种能力对于维护和扩展集群非常重要。以下是Hadoop节点动态上线下线的大概操作步骤&#xff1a; 动态…...

绽放光彩的小程序 UI 风格

绽放光彩的小程序 UI 风格...

电脑文件夹怎么加密?文件夹加密的5种方法

在数字化时代&#xff0c;信息安全显得尤为重要。对于个人电脑用户来说&#xff0c;文件夹加密是一种有效保护隐私和数据安全的方法。本文将介绍五种文件夹加密的方法&#xff0c;帮助您更好地保护自己的重要文件。 如何设置文件夹密码方法一&#xff1a;利用Windows系统自带的…...

异步复位同步释放

目录 描述 输入描述&#xff1a; 输出描述&#xff1a; 参考代码 描述 题目描述&#xff1a; 请使用异步复位同步释放来将输入数据a存储到寄存器中&#xff0c;并画图说明异步复位同步释放的机制原理 信号示意图&#xff1a; clk为时钟 rst_n为低电平复位 d信号输入…...

JupyterLab使用指南(七):JupyterLab使用 LaTeX 生成数学公式

在 JupyterLab 中&#xff0c;可以使用 LaTeX 语法生成复杂的数学公式。JupyterLab 内置对 LaTeX 的支持&#xff0c;使得我们可以方便地在 notebook 中编写和展示数学公式。以下是详细的步骤和示例。 1. 使用 LaTeX 生成数学公式 LaTeX 是一种专门用于排版数学公式的语言。J…...

docker 环境部署

1.Redis部署 用docker拉取redis镜像 docker pull redis 用docker查看拉取的镜像版本号&#xff0c;这里查到的是 6.2.6 版本 docker inspect redis 通过wget指令下载对应版本的tar包&#xff0c;下载完成后解压 wget https://download.redis.io/releases/redis-6.2.6.tar.gz …...

Spring中的ContextPath总结

Spring中的ContextPath总结 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 1. ContextPath的概念 在Spring中&#xff0c;ContextPath是指Web应用程序的上下文…...

C++设计模式——Composite组合模式

一&#xff0c;组合模式简介 真实世界中&#xff0c;像企业组织、文档、图形软件界面等案例&#xff0c;它们在结构上都是分层次的。将系统分层次的方式使得统一管理和添加不同子模块变得容易&#xff0c;在软件开发中&#xff0c;组合模式的设计思想和它们类似。 组合模式是…...

Android提供的LruCache类简介(1)

* If your cached values hold resources that need to be explicitly released, * override {link #entryRemoved}. * 如果你cache的某个值需要明确释放&#xff0c;重写entryRemoved() * If a cache miss should be computed on demand for the corresponding keys, * ov…...

【分布式系列】分布式锁timeout了怎么办?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

System.getProperty()方法总结

System.getProperty()方法总结 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;System.getProperty()方法是Java中用于获取系统属性的方法之一。它允许我们访问J…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...