skiplist

前言:在看代码时看到 ZSKIPLIST_MAXLEVEL = 32,当时并不了解 ZSKIPLIST_P 的作用,想着用 2 分法不应该层数是 64 吗?书上和他人的代码都是基于 5.0 的(5.0 上是 64),于是好奇后面又为什么改了。于是查了一些资料做下记录

为什么 ZSKIPLIST_MAXLEVEL = 32 ?

https://stackoverflow.com/questions/60017681/is-zskiplist-maxlevel64-enough-for-264-elements

ZSKIPLIST_MAXLEVEL 和 ZSKIPLIST_P 相关:

  1. 将元素个数上限设置在 264 已经是一个很大的值了,如果再大也存不下了,因为用来表示总数的 length 的类型 unsigned long 在 64 位机器上也只有 8 个字节
  2. 平均每两个节点升一层,那么需要 64 层;而如果平均每四个节点升一层,只需要 32 层,此时相对于 n 层,有 1/4 的节点有 n+1 层,因此 ZSKIPLIST_P=0.25。
    • 至于为什么取 1/4,下方提供的论文中作者在 Choosing p 中作了说明

跳表原理

https://homepage.cs.uiowa.edu/~ghosh/skip.pdf

  • d:

    • 优点:

      相对于 c: d 的每一层的指针都指向距离它 2level 位置的节点, 因此将搜索的时间复杂度降到了 O(log2n)
    • 缺点:

      增删改节点时,需要时刻考虑修改跳表中其他节点的层数,增大修改的难度
  • e:

    • 优点:

      从 d 中可以找到规律,由于层数只会在索引为 2 的倍数的节点上增加,因此索引为单数的节点的层数只有一层,共有 50% 个这样的节点,层数为 2 的节点数量为 25%, 以此类推……因此,不妨将所有节点的层数打乱,但仍然保持这样的比例分配,并且无法修改节点的层数。这样,增删改节点时,都不会影响其他节点的层数。
    • 缺点:

      就像抛硬币一样,尽管理论上正反面的概率都是 50%,但那也是在大量实验后才会逐渐接近这个理论值。如果节点比较少,计算得到的层数都很低或都很高,那么就变成了一条链表,时间复杂度降低到 O(n)。因此,通过该方式虽然可以减少代码复杂度,但是能否准确的达到预期,这是随机的。

随机层高

实现 e 的关键部分,减小代码实现难度。

假设 d 中的底数为 3,第二层在第一层的基础上每个加一层;第三层又是在第二层的基础上每个加一层……最终,只有一层的节点比例为 \(\frac{2}{3}\), 第二层的比例为 \((1-\frac{2}{3})\cdot\frac{2}{3}\)

因此以一般情况 p 代替 \(\frac{2}{3}\),可以得到

\[P(level_{i}) = (1-p)^{i-1}p
\]
random level
func randomLevel() int {
newLevel := 1
for rand.Float64() < p { // 这里的 p 为非该层的概率
newLevel++
}
return newLevel
}

以下用 insert 方法来体现 random_level 的优势:

因为用到了随机高度,因此在插入新节点时无需改动其他节点的高度。但是需要保存所有层级比新节点低的原本可以指向新节点之后节点forward,可以在降低层级时保存每个 forward

【注】

在论文中,如果插入了含有已存在的 search key 的 element,则会替换旧的 element


func (zsl *zskiplist) Insert(score float64, ele string) *zskiplistNode {
var (
update [ZSKIPLIST_MAXLEVEL]*zskiplistNode
x *zskiplistNode
) x = zsl.header
for i := zsl.level - 1; i >= 0; i-- {
for x.level[i].forward != nil &&
(x.level[i].forward.score < score ||
(x.level[i].forward.score == score && x.level[i].forward.ele < ele)) {
x = x.level[i].forward
}
update[i] = x
} x = x.level[0].forward
if x != nil && x.score == score {
x.ele = ele
return x
} level := randomLevel()
if zsl.level < level {
for i := zsl.level; i < level; i++ {
update[i] = zsl.header
}
zsl.level = level
} x = zslCreateNode(level, score, ele)
for i := 0; i < zsl.level; i++ {
x.level[i].forward = update[i].level[i].forward
update[i].level[i].forward = x
} if update[0] == zsl.header {
x.backward = nil
} else {
x.backward = update[0]
} if x.level[0].forward == nil {
zsl.tail = x
} else {
x.level[0].forward.backward = x
} zsl.length++
return x
}

【godis】skiplist的更多相关文章

  1. 【Redis】skiplist跳跃表

    有序集合Sorted Set zadd zadd用于向集合中添加元素并且可以设置分值,比如添加三门编程语言,分值分别为1.2.3: 127.0.0.1:6379> zadd language 1 ...

  2. 【转】SkipList跳表基本原理

    增加了向前指针的链表叫作跳表.跳表全称叫做跳跃表,简称跳表.跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表.跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找.跳表不仅 ...

  3. 基于.NetCore的Redis5.0.3(最新版)快速入门、源码解析、集群搭建与SDK使用【原创】

    1.[基础]redis能带给我们什么福利 Redis(Remote Dictionary Server)官网:https://redis.io/ Redis命令:https://redis.io/co ...

  4. 【Redis】redis各类型数据存储分析

    一.简介和应用 Redis是一个由ANSI C语言编写,性能优秀.支持网络.可持久化的K-K内存数据库,并提供多种语言的API.它常用的类型主要是 String.List.Hash.Set.ZSet ...

  5. 【Redis】内部数据结构自顶向下梳理

    本博客将顺着自顶向下的思路梳理一下Redis的数据结构体系,从数据库到对象体系,再到底层数据结构.我将基于我的一个项目的代码来进行介绍:daredis.该项目中,使用Java实现了Redis中所有的数 ...

  6. Python高手之路【六】python基础之字符串格式化

    Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存.[PEP-3101] This ...

  7. 【原】谈谈对Objective-C中代理模式的误解

    [原]谈谈对Objective-C中代理模式的误解 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这篇文章主要是对代理模式和委托模式进行了对比,个人认为Objective ...

  8. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

  9. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  10. 【调侃】IOC前世今生

    前些天,参与了公司内部小组的一次技术交流,主要是针对<IOC与AOP>,本着学而时习之的态度及积极分享的精神,我就结合一个小故事来初浅地剖析一下我眼中的“IOC前世今生”,以方便初学者能更 ...

随机推荐

  1. js中各种事件监听

    html.push('<input type="button" id="autocount_' + sysTime + '" class="la ...

  2. SAP GGB1替换 发现没有替换的字段 解决

    需求 替代凭证中的付款方式为默认值 GGB1替代 如上图所示 并没有我们想到替换的zschl字段 这就需要我们去维护一张配置表 将这个字段放出来 sm30--VWTYGB01 将对应字段的勾去掉即可, ...

  3. Apache druid笔记

    Apache Duid学习笔记2 1.历史节点的查询效率与内存数据比成正比,内存越大则读取磁盘的次数越少, 历史节点内存越大总数据量越小则查询速度越快. 2.缓存机制可以选择外部和内部缓存,外部缓存如 ...

  4. 肖sir ___性能测试____多线程

    一.理论 (一) (1)多线程是Python程序中实现多任务的一种方式(2)线程是程序执行的最小单位. (3)同属一个进程的多个线程共享进程所拥有的全部资源. (二)进程和线程对比 (1)关系对比: ...

  5. JavaSE——this关键字

    this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题) 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量 方法的形参没有与成员变量同名,不带 ...

  6. WPF图片的缩放节省内存

    一.前言 正好项目用到要加载大量图片,虽然说可以使用WPF提供的自带的UI虚拟化功能,但是直接加载大量的图片到内存还是会 消耗很多的内存,而且WPF支持UI虚拟化的ListBox等容器的布局是Virt ...

  7. 登录注册的小项目对比.Net Core与 .Net Framework的一些区别

    一.需求: 1.功能只有登录.注册. 二.架构: 1.分别为 UserSys.IServices:主要有实体和对实体的配置,还有对实体的操作接口 UserSys.Services :主要是对自ISer ...

  8. appium遇到的问题

    问题1:权限问题:java.lang.SecurityException: Injecting to another application requires INJECT_EV ENTS permi ...

  9. [1] Multi-View Transformer for 3D Visual Grounding 论文精读

    参考: https://zhuanlan.zhihu.com/p/467913475 3D Visual Grounding小白调研笔记 https://zhuanlan.zhihu.com/p/34 ...

  10. ubuntu20.04开机自动运行脚本实例

    在 Ubuntu 20.04 中,/etc/rc.local 文件仍然存在,但不再默认启用,因为它已经被 systemd 代替.下面是使用systemd开机执行的脚本的实例: 1.编写脚本myscri ...