SkipList

news2024/12/22 13:58:02

文章目录

  • SkipList
    • 理解跳表从单链表说起
    • 查找的时间复杂度
    • 空间复杂度
    • 插入数据
      • 更高效的方式维护索引
      • 代码实现索引的抽取
        • 概率算法
      • 举例插入元素
    • 删除数据
    • 总结
    • 为什么Redis选择使用跳表而不是红黑树来实现有序集合

SkipList

理解跳表从单链表说起

在原始单链表中查找元素,只能从头到尾一个一个的比较往后遍历,当数据量非常大的时候,时间复杂度会很高。于是考虑为单链表添加索引,在单链表的基础上添加一级索引,在一级索引的基础上添加二级索引…
在这里插入图片描述
当数据量非常大的时候,优势就会很明显。

查找的时间复杂度

时间复杂度 = 索引的高度 * 每层索引遍历的元素的个数
原始链表有n个元素,一级索引有n/2个元素、二级索引有n/4个元素、k级索引有n/2^k
个元素。最高级索引一般有2个元素,2= n/(2^h) 即h = log2n-1;最高级索引h为索引层的高度加上原始数据一层,跳表的总高度为h = log2n。每一层最多遍历3个节点。所以查找的时间复杂度为O(3logn)省略常数即O(logn)

空间复杂度

假如原始链表包含n个元素,则一级索引元素个数为n/2、二级索引个数为n/4、三级索引元素个数为n/8以此类推,索引节点总和为n/2+n/4+n/8+…+8+4+2 = n-2空间复杂度为O(n)
如果每三个节点抽一个节点作为索引,索引总和数就是n/3+n/9+…+9+3 = n/2-1减少了一半。所以可以通过减少索引数减少空间复杂度,但是相应的肯定会造成查找效率有一定下降,可以根据我们的应用场景控制这个阈值
但是,索引节点往往只需要存储key和几个指针,并不需要存储完整的对象,所以当对象比较索引节点大很多时,索引占用的额外空间就可以忽略了。

插入数据

跳表的原始链表需要保持有序,所以会像查找元素一样,找到元素应该插入的位置,时间复杂度为O(logn)
如果一直往原始列表中添加数据,但是不更新索引,就可能出现索引节点之间数据非常多的情况,极端情况退化为单链表,从而使查找效率降低。
在这里插入图片描述比较容易理解的做法就是完全重建索引,每次插入数据都把这个跳表的索引删除掉,然后重建。因为索引的空间复杂度是O(n),所以重建的时间复杂度是O(n),造成的后果是,为了维护索引导致每次插入数据的时间复杂度变成了O(n)

更高效的方式维护索引

在原始链表中随机选取n/2个元素作为一级索引是不是也能通过索引提高查找的效率?当然可以,因为一般随机选取的元素相对来说都比较均匀。如下图所示,随机选择n/2个元素作为一级索引,虽然不是每隔一个元素抽取一个,但是对于查找效率来讲,影响不大,比如想找元素16,仍然可以通过一级索引,使得遍历路径少了将近一半,如果抽取的一级元素恰好是前一半的元素1,3,4,5,7,8那么查找效率确实没有提升。但是这样的概率太小了。可以这样认为:当原始链表中元素数量足够大,且抽取足够随机,得到的索引是均匀的
在这里插入图片描述

代码实现索引的抽取

在每次插入新元素的时候,尽量让该元素有1/2的几率建立一级索引,1/4的几率建立二级索引,1/8的几率建立三级索引,以此类推,就能满足上面的条件。需要一个算法把控这个1/2、1/4…当每次有数据要插入时,先通过概率算法告诉我们这个元素要插入到几级索引中,然后开始维护索引并把数据插入到原始链表中。

概率算法

可以实现一个randomLevel()方法,该方法随即生成1~MAX_LEVEL之间的数(MAX_LEVEL表示索引的最高层数)该方法有1/2的概率返回1,1/4的概率返回2、1/8的概率返回3

  • randomLevel()方法返回1表示当前插入的该元素不需要重建索引,只需要存储数据到原始链表中即可。
  • randomLevel()方法返回2表示当前插入的该元素需要建立一级索引,(概率为1/4)
  • randomLevel()方法返回3表示当前插入的该元素需要建立二级索引,(概率为1/8)
  • randomLevel()方法返回4表示当前插入的该元素需要建立三级索引,(概率为1/16)

  • 当建立二级索引的时候,同时也会建立一级索引;当建立三级索引的时候,同时也会建立一二级索引。所以一级索引的个数等于 = 原始链表元素个数 * [randomLevel()>1的概率]
    也就是n ** (1-1/2) = n/2;以此类推…
//该randomLevel()算法会随机生成一个1~maxlevel之间的数,且:
//  1/2的概率返回1
//  1/4的概率返回2
//  1/8的概率返回3
int randomLevel(){
	int level = 1;
	//当level < MAX_LEVEL,且随机数小于设定的晋升概率时,level+1
	while(random()<SKIPLIST_P && level < MAX_LEVEL){
	//random()产生0-1中间的随机数
		level+=1;
	}
	return level;
}

这里的SKIPLIST_P为1/2;如果想节省空间利用率可以适当降低SKIPLIST_P的值从而减少索引个数REdis的zset中的SKIPLIST设定为0.25
Redis zslRandomLevel实现原理
元素插入到单链表的时间复杂度为O(1),索引的高度最多为logn当插入一个元素x时,最坏的情况就是元素x需要插入到每层索引中,最坏的时间复杂度为O(logn)

举例插入元素

要插入数据 6 到跳表中,首先 randomLevel() 返回 3,表示需要建二级索引,即:一级索引和二级索引需要增加元素 6。该跳表目前最高三级索引,首先找到三级索引的 1,发现 6 比 1大比 13小,所以,从 1 下沉到二级索引。
在这里插入图片描述
下沉到二级索引后,发现 6 比 1 大比 7 小,此时需要在二级索引中 1 和 7 之间加一个元素6 ,并从元素 1 继续下沉到一级索引
在这里插入图片描述
下沉到一级索引后,发现 6 比 1 大比 4 大,所以往后查找,发现 6 比 4 大比 7 小,此时需要在一级索引中 4 和 7 之间加一个元素 6 ,并把二级索引的 6 指向 一级索引的 6,最后,从元素 4 继续下沉到原始链表。
在这里插入图片描述
下沉到原始链表后,就比较简单了,发现 4、5 比 6小,7比6大,所以将6插入到 5 和 7 之间即可,整个插入过程结束。
在这里插入图片描述

删除数据

时间复杂度为O(logn)。删除节点其对应的索引也要删除
在这里插入图片描述

总结

  • 跳表是实现二分查找的有序链表
  • 每个元素插入时随机生成它的level
  • 最低层包含所有元素
  • 如果一个元素出现在level(x)那么它一定出现在x以下的level中
  • 每个索引节点包含两个指针一个向下一个向右(但是各种跳表源码包括redis的zset都没有向下的指针)
  • 跳表查询删除插入的时间复杂度都是O(logn)与平衡二叉树接近

为什么Redis选择使用跳表而不是红黑树来实现有序集合

插入一个元素
删除一个元素
查找一个元素
有序输出所有元素
按照范围区间查找元素(比如[100,356])
其中前四个操作红黑树也可以完成,且时间复杂度跟跳表是一样的但是,按照区间查找数据这个操作,红黑树的效率没有跳表高。

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

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

相关文章

C# Linq 详解三

目录 概述 十三、Sum / Min / Max / Average 十四、Distinct 十五、Concat 十六、Join 十七、ToList 十八、ToArray 十九、ToDictionary C# Linq 详解一 1.Where 2.Select 3.GroupBy 4.First / FirstOrDefault 5.Last / LastOrDefault C# Linq 详解二 1.OrderBy 2.O…

HOT64-搜索二维矩阵

leetcode原题链接&#xff1a;搜索二维矩阵 题目描述 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非递减顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回…

Leetcode每日一题:979. 在二叉树中分配硬币(2023.7.14 C++)

目录 979. 在二叉树中分配硬币 题目描述&#xff1a; 实现代码与解析&#xff1a; dfs&#xff08;后序遍历&#xff09; 原理思路&#xff1a; 979. 在二叉树中分配硬币 题目描述&#xff1a; 给定一个有 N 个结点的二叉树的根结点 root&#xff0c;树中的每个结点上都对…

宋浩高等数学笔记(一)函数与极限

b站宋浩老师的高等数学网课&#xff0c;全套笔记已记完&#xff0c;不定期复习并发布更新。 章节顺序与同济大学第七版教材所一致。

C++虚函数学习

VC6新建一个单文档工程&#xff1b; 添加一个一般类&#xff1b; 生成的Shape.cpp保持不变&#xff1b; #include "Shape.h"#ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]__FILE__; #define new DEBUG_NEW #endif// // Construction/Destruction //Shap…

Unity平台如何实现RTSP转RTMP推送?

技术背景 Unity平台下&#xff0c;RTSP、RTMP播放和RTMP推送&#xff0c;甚至包括轻量级RTSP服务这块都不再赘述&#xff0c;今天探讨的一位开发者提到的问题&#xff0c;如果在Unity下&#xff0c;实现RTSP播放的同时&#xff0c;随时转RTMP推送出去&#xff1f; RTSP转RTMP…

使用Google Chrome浏览器打开Vue项目报错“Uncaught runtime errors”——已解决

使用Google Chrome浏览器打开Vue项目报错&#xff1a; Uncaught runtime errors:ERROR Identifier originalPrompt has already been declared SyntaxError: Identifier originalPrompt has already been declared问题原因&#xff1a; Google Chrome浏览器安装了插件跟Vue项…

2023年最新水果编曲软件FLStudio21.0.3.3517中文直装完整至尊解版下载

2023年最新水果编曲软件FLStudio21.0.3.3517中文直装完整至尊解版下载 是最好的音乐开发和制作软件也称为水果循环。它是最受欢迎的工作室&#xff0c;因为它包含了一个主要的听觉工作场所。 最新fl studio 21有不同的功能&#xff0c;如它包含图形和音乐音序器&#xff0c;帮助…

Nginx Linux设置开机自启动

使用如下命令 vi /lib/systemd/system/nginx.service 创建并编辑文件将以下代码黏贴至此文件中 [Unit] Descriptionnginx Afternetwork.target[Service] Typeforking TimeoutSec0 #防止启动超时 Userroot Grouproot criptionnacos Afternetwork.target[Service] Typeforking T…

哈希的应用(1)——位图

计算机存储单位的常用知识 2^30大约等于10亿 1byte8bit--一个字节等于八个比特位 左移操作符<<表示将值从底地址到高地址的方向移动。 bitset<-1>&#xff0c;开了2^32个bit512MB1GB 位图概念 面试题 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符…

Kerberos协议详解

0x01 kerberos协议的角色组成 Kerberos协议中存在三个角色&#xff1a; 客户端(Client)&#xff1a;发送请求的一方 服务端(Server)&#xff1a;接收请求的一方 密钥分发中心(Key distribution KDC) 密钥分发中心分为两个部分&#xff1a; AS(Authentication Server)&…

Linux下JDK版本与安装版本不一致问题

目录 一. &#x1f981; 前言二. &#x1f981; 操作流程三. &#x1f981; 总结四. &#x1f981; Happy Ending 一. &#x1f981; 前言 最近重新安装了centos7.9,针对以前遇到的Java版本不一致的情况, 提出了另一种方法,该方法简单易行,容易理解。 二. &#x1f981; 操作…

吴恩达机器学习2022-Jupyter1可选实验室: Python 和 Jupyter 笔记本简介

欢迎来到第一个可选实验室&#xff01; 可供选择的实验室包括:提供信息-比如这个笔记本以实际例子加强课堂教材提供分级实验室常规的工作实例 1.1 目标 在本实验中&#xff0c;您将: 对Jupyter笔记本进行简要介绍&#xff0c;参观Jupyter笔记本&#xff0c;了解标记单元格和…

pytorch实现线性回归

转大佬笔记 代码&#xff1a; # -*- coding: utf-8 -*- # Time : 2023-07-14 14:57 # Author : yuer # FileName: exercise05.py # Software: PyCharm import matplotlib.pyplot as plt import torch# x,y是3行1列的矩阵&#xff0c;所以在[]中要分为3个[] x_data torch.…

人物专访 |时静:携手The Open Group,把握时代脉动,助力中国数字经济建设

​ 在由The Open Group主办的2023架构可持续未来峰会上&#xff0c;The Open Group与机械工业出版社进行了战略签约合作仪式&#xff0c;并就备受业界期待的TOGAF标准第10版中文图书发布&#xff0c;以及OPA标准2.1版的本地化工作展开具体合作。 对此&#xff0c;机械工业出版社…

Video Enhancement with Task-Oriented Flow

摘要 Many video enhancement algorithms rely on optical flow to register frames in a video sequence. Precise flow estimation is however intractable; and optical flow itself is often a sub-optimal representation for particular video processing tasks. In thi…

嵌入式linux驱动开发之移远4G模块EC800驱动移植指南

回顾下移远4G模块移植过程&#xff0c; 还是蛮简单的。一通百通&#xff0c;无论是其他4G模块都是一样的。这里记录下过程&#xff0c;分享给有需要的人。环境使用正点原子的imax6ul开发板&#xff0c;板子默认支持中兴和移远EC20的驱动&#xff0c;这里要移植使用的是移远4G模…

在Linux下做性能分析1:基本模型

介绍 本Blog开始介绍一下在Linux分析性能瓶颈的基本方法。主要围绕一个基本的分析模型&#xff0c;介绍perf和ftrace的使用技巧&#xff0c;然后东一扒子&#xff0c;西一扒子&#xff0c;逮到什么说什么&#xff0c;也不一定会严谨。主要是把这个领域的一些思路和技巧串起来。…

Electron + vue 搭建桌面客户端

下载Electron 压缩包&#xff0c;放到本地 Electron 压缩包下载地址 cd ~/Library/Caches/electron

Duilib 父窗口无效化和消息传递

文章目录 1、父窗口无效化和消息传递2、EnableWindow()和SetFocus()的含义和用法 1、父窗口无效化和消息传递 当使用duillib界面库时&#xff0c;我们往往需要建立多个窗口&#xff0c;子窗口和父窗口之间有一定的逻辑需要&#xff0c;比如当子窗口弹出时&#xff0c;让父窗口…