redis——为什么选择了跳表而不是红黑树?

跳表是个啥东西请看这个文章。

我们知道,节点插入时随机出一个层数,仅仅依靠一个简单的随机数操作而构建出来的多层链表结构,能保证它有一个良好的查找性能吗?为了回答这个疑问,我们需要分析skiplist的统计性能。

在分析之前,我们还需要着重指出的是,执行插入操作时计算随机数的过程,是一个很关键的过程,它对skiplist的统计特性有着很重要的影响。这并不是一个普通的服从均匀分布的随机数,它的计算过程如下:

  • 首先,每个节点肯定都有第1层指针(每个节点都在第1层链表里)。
  • 如果一个节点有第i层(i>=1)指针(即节点已经在第1层到第i层链表中),那么它有第(i+1)层指针的概率为p。
  • 节点最大的层数不允许超过一个最大值,记为MaxLevel。

这个计算随机层数的伪码如下所示:

randomLevel()
level := 1
// random()返回一个[0...1)的随机数
while random() < p and level < MaxLevel do
level := level + 1
return level

randomLevel()的伪码中包含两个参数,一个是p,一个是MaxLevel。在Redis的skiplist实现中,这两个参数的取值为:

p = 1/4
MaxLevel = 32

skiplist的算法性能分析

在这一部分,我们来简单分析一下skiplist的时间复杂度和空间复杂度,以便对于skiplist的性能有一个直观的了解。如果你不是特别偏执于算法的性能分析,那么可以暂时跳过这一小节的内容。

我们先来计算一下每个节点所包含的平均指针数目(概率期望)。节点包含的指针数目,相当于这个算法在空间上的额外开销(overhead),可以用来度量空间复杂度。

根据前面randomLevel()的伪码,我们很容易看出,产生越高的节点层数,概率越低。定量的分析如下:

  • 节点层数至少为1。而大于1的节点层数,满足一个概率分布。
  • 节点层数恰好等于1的概率为1-p。
  • 节点层数大于等于2的概率为p,而节点层数恰好等于2的概率为p(1-p)。
  • 节点层数大于等于3的概率为p^2,而节点层数恰好等于3的概率为p^2(1-p)。
  • 节点层数大于等于4的概率为p^3,而节点层数恰好等于4的概率为p^3(1-p)。
  • ......

因此,一个节点的平均层数(也即包含的平均指针数目),计算如下:

现在很容易计算出:

  • 当p=1/2时,每个节点所包含的平均指针数目为2;
  • 当p=1/4时,每个节点所包含的平均指针数目为1.33。这也是Redis里的skiplist实现在空间上的开销。

接下来,为了分析时间复杂度,我们计算一下skiplist的平均查找长度。查找长度指的是查找路径上跨越的跳数,而查找过程中的比较次数就等于查找长度加1。以前面图中标出的查找23的查找路径为例,从左上角的头结点开始,一直到结点22,查找长度为6。

为了计算查找长度,这里我们需要利用一点小技巧。我们注意到,每个节点插入的时候,它的层数是由随机函数randomLevel()计算出来的,而且随机的计算不依赖于其它节点,每次插入过程都是完全独立的。所以,从统计上来说,一个skiplist结构的形成与节点的插入顺序无关。

这样的话,为了计算查找长度,我们可以将查找过程倒过来看,从右下方第1层上最后到达的那个节点开始,沿着查找路径向左向上回溯,类似于爬楼梯的过程。我们假设当回溯到某个节点的时候,它才被插入,这虽然相当于改变了节点的插入顺序,但从统计上不影响整个skiplist的形成结构。

现在假设我们从一个层数为i的节点x出发,需要向左向上攀爬k层。这时我们有两种可能:

  • 如果节点x有第(i+1)层指针,那么我们需要向上走。这种情况概率为p。
  • 如果节点x没有第(i+1)层指针,那么我们需要向左走。这种情况概率为(1-p)。

preview

用C(k)表示向上攀爬k个层级所需要走过的平均查找路径长度(概率期望),那么:

C(0)=0
C(k)=(1-p)×(上图中情况b的查找长度) + p×(上图中情况c的查找长度)

代入,得到一个差分方程并化简:

C(k)=(1-p)(C(k)+1) + p(C(k-1)+1)
C(k)=1/p+C(k-1)
C(k)=k/p

这个结果的意思是,我们每爬升1个层级,需要在查找路径上走1/p步。而我们总共需要攀爬的层级数等于整个skiplist的总层数-1。

那么接下来我们需要分析一下当skiplist中有n个节点的时候,它的总层数的概率均值是多少。这个问题直观上比较好理解。根据节点的层数随机算法,容易得出:

  • 第1层链表固定有n个节点;
  • 第2层链表平均有n*p个节点;
  • 第3层链表平均有n*p^2个节点;
  • ...

所以,从第1层到最高层,各层链表的平均节点数是一个指数递减的等比数列。容易推算出,总层数的均值为log1/pn,而最高层的平均节点数为1/p。

综上,粗略来计算的话,平均查找长度约等于:

  • C(log1/pn-1)=(log1/pn-1)/p

即,平均时间复杂度为O(log n)。

当然,这里的时间复杂度分析还是比较粗略的。比如,沿着查找路径向左向上回溯的时候,可能先到达左侧头结点,然后沿头结点一路向上;还可能先到达最高层的节点,然后沿着最高层链表一路向左。但这些细节不影响平均时间复杂度的最后结果。另外,这里给出的时间复杂度只是一个概率平均值,但实际上计算一个精细的概率分布也是有可能的。

详情还请参见William Pugh的论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》。

skiplist与平衡树、哈希表的比较

  • skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。
  • 在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
  • 平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
  • 从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
  • 查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。
  • 从算法实现难度上来比较,skiplist比平衡树要简单得多。

Redis中的skiplist和经典有何不同

  • 分数(score)允许重复,即skiplist的key允许重复。这在最开始介绍的经典skiplist中是不允许的。
  • 在比较时,不仅比较分数(相当于skiplist的key),还比较数据本身。在Redis的skiplist实现中,数据本身的内容唯一标识这份数据,而不是由key来唯一标识。另外,当多个元素分数相同的时候,还需要根据数据内容来进字典排序。
  • 第1层链表不是一个单向链表,而是一个双向链表。这是为了方便以倒序方式获取一个范围内的元素。
  • 在skiplist中可以很方便地计算出每个元素的排名(rank)。

作者的话

最后我们看看,对于这个问题,Redis的作者 @antirez 是怎么说的:

There are a few reasons:

1) They are not very memory intensive. It's up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees.

2) A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.

3) They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch (already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.

有几个原因:

1)它们的记忆力不是很强。基本上由你决定。更改有关节点具有给定数量级别的概率的参数将使内存密集度低于btree。

2)排序集通常是许多Zrange或Zrevrange操作的目标,即作为链表遍历跳过列表。通过此操作,跳过列表的缓存区域性至少与其他类型的平衡树一样好。

3)它们易于实现、调试等。例如,由于跳过列表的简单性,我收到了一个补丁(已经在redis master中),其中包含在o(log(n))中实现zrank的扩展跳过列表。它只需要对代码稍作修改。

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

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

相关文章

机器学习公式推导

文章目录线性回归逻辑回归线性判别分析PCAk-means决策树svm随机深林GBDTxgboost强化学习MapReduce线性回归 逻辑回归 对于分类问题&#xff1a;输出0/1&#xff0c;超过[0,1]没有意义&#xff0c;使用sigmoid函数 **代价函数&#xff1a;**使用L2平方差&#xff0c;由于模型函…

Python综合应用(1)--名片管理系统开发

第一个综合应用-名片管理系统1框架搭建2完善功能综合应用&#xff0c;名片管理系统 欢迎界面&#xff0c;不同选项&#xff0c;1.新建名片&#xff0c;2.显示全部&#xff0c;3 查询名片&#xff08;查到之后可以修改名片信息&#xff09;&#xff0c;0 退出系统 程序开发流程…

springboot1——spring相关入门

spring 随着我们开发&#xff0c;发现了一个问题&#xff1a; A---->B---->C---->D 在A中创建B的对象调用B的资源 在B中创建C的对象调用C的资源 在C中创建D的对象调用…

大数据学习(06)-- 云数据库

文章目录目录1.什么是云数据库&#xff1f;1.1 云计算和云数据库的关系1.2 云数据库的概念1.3 云数据库的特性1.4 云数据库应用场景1.5 云数据库和其他数据的关系2.云数据库产品有哪些&#xff1f;2.1 云数据库厂商概述2.2 亚马逊云数据库产品2.3 Google云数据库产品2.4 微软云…

Python(21)--变量进阶

变量的进阶使用1变量引用2可变、不可变数据类型3局部变量和全局变量4.Tips本系列博文来自学习《Python基础视频教程》笔记整理&#xff0c;视屏教程连接地址&#xff1a;http://yun.itheima.com/course/273.html在博文&#xff1a;https://blog.csdn.net/sinat_40624829/articl…

机器学习知识总结系列-机器学习中的数学-矩阵(1-3-2)

矩阵 SVD 矩阵的乘法状态转移矩阵状态转移矩阵特征值和特征向量 对称阵 正交阵 正定阵数据白化矩阵求导 向量对向量求导 标量对向量求导 标量对矩阵求导一.矩阵1.1 SVD奇异值分解&#xff08;Singular Value Decomposition&#xff09;&#xff0c;假设A是一个mn阶矩阵&#xf…

面试中海量数据处理总结

教你如何迅速秒杀掉&#xff1a;99%的海量数据处理面试题 前言 一般而言&#xff0c;标题含有“秒杀”&#xff0c;“99%”&#xff0c;“史上最全/最强”等词汇的往往都脱不了哗众取宠之嫌&#xff0c;但进一步来讲&#xff0c;如果读者读罢此文&#xff0c;却无任何收获&…

redis——旧版复制

Redis 的复制功能分为同步&#xff08;sync&#xff09;和命令传播&#xff08;command propagate&#xff09;两个操作&#xff1a; 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态。命令传播操作用于在主服务器的数据库状态被修改&#xff0c; 导致…

Linux(3)-网-ifconfig,ping,ssh

终端命令网-ping,ssh1. ifconfig -a2. ping3. ssh3.1安装3.2 连接3.3 配置登入别名防火墙端口号,todo1. ifconfig -a 查看IP地址&#xff0c; 还可以用于配置网口。 ifconfig -a 2. ping ping命令&#xff1a; 检测到IP地址的连接是否正常。命令开始后由本机发送数据包a&…

redis——相关问题汇总

什么是redis&#xff1f; Redis 本质上是一个 Key-Value 类型的内存数据库&#xff0c; 整个数据库加载在内存当中进行操作&#xff0c; 定期通过异步操作把数据库数据 flush 到硬盘上进行保存。 因为是纯内存操作&#xff0c; Redis 的性能非常出色&#xff0c; 每秒可以处理…

一文搞定面试中的二叉树问题

一文搞定面试中的二叉树问题 版权所有&#xff0c;转载请注明出处&#xff0c;谢谢&#xff01; http://blog.csdn.net/walkinginthewind/article/details/7518888 树是一种比较重要的数据结构&#xff0c;尤其是二叉树。二叉树是一种特殊的树&#xff0c;在二叉树中每个节点…

无数踩坑系列(1)--Brightness Controller

Brightness Controller1.尝试找回系统自带亮度调节条1.1 配置grub文件&#xff0c;无效1.2 使用命令调节屏幕亮度&#xff0c;无效2.安装应用程序Brightness Controller2.1许多博文都写出了如下方案&#xff0c;无效&#xff1a;2.2 github 手动安装https://github.com/LordAmi…

springboot2——MyBatis入门

原生缺陷: 数据库dao层操作缺陷: ①jdbc的增删改查代码的冗余过大&#xff0c;查询的时候需要遍历。 ②Sql语句和数据库相关参数和代码的耦合性过高。 解决:使用Mybatis 业务层缺陷: ①业务层和数据…

Linux(4)-资源-du,top,free,gnome

Linux终端命令1.磁盘资源1.1 df -hl1.2 du1.3 统计文件数量2.缓存资源2.1 top2.2 free -m3.Gnome3.1系统监视器-gnome-system-monitor3.2 截屏--screenshot查看文件系统资源的一些命令1.磁盘资源 1.1 df -hl 查看分区磁盘使情况 硬盘空间不够时&#xff0c;跑程序会报错&…

redis——Java整合

redis官网 微软写的windows下的redis 我们下载第一个 额案后基本一路默认就行了 安装后&#xff0c;服务自动启动&#xff0c;以后也不用自动启动。 出现这个表示我们连接上了。 redis命令参考链接 Spring整合Redis 引入依赖 - spring-boot-starter-data-redis <depend…

一文理解KMP算法

一文理解KMP算法 作者&#xff1a;July 时间&#xff1a;最初写于2011年12月&#xff0c;2014年7月21日晚10点 全部删除重写成此文&#xff0c;随后的半个多月不断反复改进。后收录于新书《编程之法&#xff1a;面试和算法心得》第4.4节中。 1. 引言 本KMP原文最初写于2年多前的…

小猫的java基础知识点汇总(下)

1、线程和进程有什么区别&#xff1f; 进程是操作系统资源分配的基本单位&#xff0c;而线程是任务调度和执行的基本单位 线程是进程的子集&#xff0c;一个进程可以有很多线程&#xff0c;每条线程并行执行不同的任务。 不同的进程使用不同的内存空间&#xff0c;而所有的线…

小猫的java基础知识点汇总(上)

1、一个".java"源文件中是否可以包括多个类&#xff08;不是内部类&#xff09;&#xff1f;有什么限制&#xff1f; 可以有多个类&#xff0c;但只能有一个public的类&#xff0c;并且public的类名必须与文件名相一致。 2、short s1 1; s1 s11; 有没有错&#xff…

后端 分页组件实例

/*** 分页相关信息*/ public class Page {//当前页码private int current1;//显示的上限private int limit10;//数据总数//用于计算页数private int rows;//路径private String path;public int getCurrent() {return current;}public void setCurrent(int current) {if (curre…

大数据学习(07)--MapReduce

文章目录目录1.MapReduce介绍1.1 什么是分布式并行编程&#xff1f;1.2 MapReduce模型介绍1.3 map和reduce函数2.MapReduce体系架构3.MapReduce工作流程3.1 概述3.2 MapReduce各个阶段介绍3.3 shuffle过程介绍3.3.1 shuffle过程简介3.3.2 map中的shuffle过程3.3.3 reduce中的sh…