跳跃列表 -- SkipList

前言

我们先回顾一下在有序序列中查找某个特定元素的情境:

  • 如果该序列使用支持随机访问的数组存储,那我们通过二分查可以很快地找到某个元素。
  • 但是考虑到增加、删除的效率以及内存空间的使用,很多时候我们会使用不支持随机访问的链表存储,那么我们查询某个元素时就只能遍历整个链表。
  • 综合考虑查询和增加、删除操作,我们通常会使用二叉搜索树存储,这样我们就可以不依赖随机访问特性来进行二分查找了。
  • 普通的二叉搜索树插入元素越有序效率越低,最坏情况会退化回链表,为了解决这个问题,我们采用平衡二叉树来存储,以保证在任何情况下增删查操作的复杂度都为O(logn)。
  • 平衡树自平衡的过程实现比较复杂,比较难进行扩展。

那有没有实现相对简单、但是复杂度与平衡树相近的方法呢?答案是有的,跳跃列表的结构就是这样的,实现相对简单、但是复杂度与平衡树相近。

跳跃列表

概念:

跳跃列表(简称跳表)是一种可以代替平衡树的数据结构,默认是按照key升序,跳表让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过“空间来换取时间”的一个算法,在每个节点中增加了向前的指针,在插入、删除、查找时可以忽略一些不可能涉及到的结点,从而提高了效率。与平衡树相比:跳表的实现相对简单,更容易扩展。

特性:

  • 由多层组成,每层都是一个有头节点的有序(默认升序)链表。
  • 第1层(最底层)的链表包含跳表中所有的元素。
  • 如果某个元素在第k层出现,那么在第1~k-1层也必定都会出现,即每一层都是下面各层的子集。
  • 链表的每个节点包含两个指针,一个指向同链表中下一个元素,一个指向同元素下层的指针。
  • 新增元素时,首先会在第1层插入元素,然后根据随机算法来判断其它层是否需要插入。

 

原理:

  • 与索引的原理类似,通过“空间换时间”的思路,每个节点都有可以跨越多个元素的指针,通过这些指针,第k层可以视为第k-1级索引,这样就加快了查找/插入/删除的速度。
  • 为了避免占用空间过多,除第1层外,其它层都不存储实际数据,只有包含指向同链表中下一个元素的指针和同元素下层的指针。
  • 以升序跳表为例:
    • 当查找元素时,从最顶层链表的头节点开始遍历,如果当前节点的下一个节点包含的值比目标元素值小,则继续向右查找。
    • 如果下一个节点的值比目标值大,就转到当前层的下一层去查找。重复向右和向下的操作,直到找到与目标值相等的元素为止。
  • 图示:跳表中查找元素21的过程:


 

redis为什么不用红黑树作为zset底层实现?

  • 跳表和红黑树在 内存占用对范围查找(zset的主要使用场景)的支持 方面都差不多, 但是跳表的实现更简单,更易于扩展,故跳表比红黑树更适合。
  • 原作者这样说:
    • 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.
    • 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.
    • 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.
    • About the Append Only durability & speed, I don’t think it is a good idea to optimize Redis at cost of more code and more complexity for a use case that IMHO should be rare for the Redis target (fsync() at every command). Almost no one is using this feature even with ACID SQL databases, as the performance hint is big anyway.
    • About threads: our experience shows that Redis is mostly I/O bound. I’m using threads to serve things from Virtual Memory. The long term solution to exploit all the cores, assuming your link is so fast that you can saturate a single core, is running multiple instances of Redis (no locks, almost fully scalable linearly with number of cores), and using the “Redis Cluster” solution that I plan to develop in the future.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值