跳跃列表

作者: 不二不二熊 | 来源:发表于2018-09-26 11:32 被阅读4次

一、业务场景

某游戏开发公司,boss交代程序员开发一套拍卖行系统,用来查阅和出售游戏中的道具。拍卖行商品可以按照等级、价格等排序并展示给玩家,支持全名称的精确搜索和不输入名称的全量查询。

拍卖行系统

游戏中的商品动辄几十万的数量,如果是精确搜索还好办,可以直接查询数据库,但是如果是全量搜索的话,不仅要将数据库中所有的记录查询出来,还要支持排序。因此我们可以提前在内存中存储有序的全量商品集合,每一种排序方式都保存成独立的集合,根据不同请求的排序种类,返回不同的集合结果。

假设在当时的环境下并没有redis这样的内存数据库,你会选择什么样的数据存储结构呢?

二、分析

拍卖行商品列表是线性的,因此我们首先考虑到的是数组和链表,可是仔细考虑,无论是数组还是链表,在插入新商品的时候,都会存在性能问题。

以按照商品等级排序为例,假如我们使用数组结构,那么我们在插入新商品的时候,首先我们需要知道插入商品的位置。这里我们可以使用二分查找法,时间复杂度为O(logN)。在插入的过程中,我们需要将插入位置之后的数据全部后移一位,这一步时间复杂度为O(N),所以总体的时间复杂度为O(N)。

假如我们使用链表结构,我们同样的首先需要知道插入商品的位置,这里我们无法使用二分查找法,只能逐节点比较,时间复杂度为O(N)。插入的过程倒是容易,我们只需要改变节点指向的目标位置,这一步的时间复杂度为O(1),因此总体时间复杂度为O(N)。

相对于几十万的商品集合来说,这两种方法显然都太慢了。那么有没有一种数据结构能够提高性能呢?

三、跳跃列表

跳跃列表(skipList)是一种基于有序列表的扩展,简称跳表。

在详细解释跳跃列表之前,我们不妨思考这样一个问题:怎样才能跟更快的查找到一个有序列表的某一个节点呢?

我们可以利用类似索引的思想,提取中链表中的部分关键节点。比如给定一个1>2>3>5>6>7的有序链表,我们可以提取中中间的奇数作为关键节点。假如我们此时需要插入元素4,我们无需逐一比较,只需要比较关键节点3、5、7。在确定新节点在关键节点中的位置,我们就可以在原链表中,迅速定位到节点并且插入。数据量小的情况下,优化效果不是十分明显,如果链表中有1w甚至10w个节点,比较次数就整整减少了一半!这样的做法虽然增加了50%的空间,但是性能提高了一倍。

现在我们可以进一步思考,我们能否再已经提取的索引基础上,进一步提取出一层索引的索引?事实上这是可以的,当有了二级索引后,新节点可以先和二级索引比较,确定大体范围,然后再跟一级索引比较,最后回到原链表,插入节点。当节点很多的时候,比较次数就能减少到原来的四分之一。我们可以利用这样的思想,不断向上抽取,保证每一层是上层节点的一半,至于提取的极限,是当同一层只有两个节点的时候,因为一个节点没有比较的意义。这样的多层链表结构,就是所谓的【跳跃表】。

跳跃列表的"提拔"机制

这时候出现了一个问题,当大量的数据节点被插入到原链表中,上层的节点会逐渐变得不够用。这时候需要从新节点中选取一部分提到上一层,可是究竟应该"提拔"谁,忽略谁呢?

跳跃列表的设计者采取了一种有趣的方案:【抛硬币】。也就是随机决定新节点是否提拔,每向上提拔一层的概率是50%。

至于为什么要采取这么奇怪的方法,这是因为跳跃列表的添加和删除节点是不可预测的,我们很难使用一种有效的算法来保证所索引始终均匀。随机抛硬币的方法虽然不能保证索引绝对均匀分布,却可以让索引大体趋于均匀。

插入

跳跃列表插入节点的流程大致可以分为以下几步:

  1. 新节点和各层索引节点逐层比较,确定原链表的插入位置,时间复杂度为O(logN)
  2. 将元素插入到原链表,时间复杂度为O(1)
  3. 利用抛硬币的方式提拔节点为上一层索引,时间复杂度为O(logN)

综上所述,跳跃列表插入的操作的时间复杂度为O(logN),而这种数据结构所占空间是2N,即空间复杂度为O(N)。

删除

跳跃列表删除节点大致可以分为以下几步:

  1. 自上而下,查找第一次出现节点的索引,并逐层找到每一层对应的节点。O(logN)

  2. 删除每一层查找到的节点,如果该层只剩下1个节点,删除整个一层(原链表除外)。O(logN)

总体上,跳跃表删除操作的时间复杂度是O(logN)

四、跳跃列表VS二叉查找树

跳跃列表的优点是维持结构平衡的成本比较低,完全依靠随机。而二叉查找树在多次插入删除后,需要rebalance来重新调整结构平衡。

没有绝对优劣的数据结构,关键还是要看应用场景。

这一解决方案和Redis当中的Sorted-set不谋而合。而Sorted-set这种有序集合,正是对于跳跃表的改进和应用。而对于关系型数据库如何维护有序的记录集合呢?他们使用的是B+树。有关B+树的知识,将在以后的章节中提到。

相关文章

  • 跳跃列表

    一、业务场景 某游戏开发公司,boss交代程序员开发一套拍卖行系统,用来查阅和出售游戏中的道具。拍卖行商品可以按照...

  • 深入理解跳表

    1.跳跃链表的基本概念 初识跳表 跳跃列表是一种数据结构。它允许快速查询一个有序连续元素的数据链表。跳跃列表的平均...

  • 数据结构与算法——跳表

    什么是跳表 跳表全称为跳跃列表,它允许快速查询,插入和删除一个有序连续元素的数据链表。跳跃列表的平均查找和插入时间...

  • 跳表

    跳表 跳表,又叫做跳跃表、跳跃列表,在有序链表的基础上增加了“跳跃”的功能 设计的初衷是为了取代平衡树(比如红黑树...

  • 8.1 对象

    Redis用到的所有主要数据结构,简单动态字符串(SDS)、双端列表、字典、跳跃表、整数集合、压缩列表。Redis...

  • redis数据结构

    redis数据结构列表:简单动态字符串(sds)链表(list)字典(dict)跳跃表(skiplist)整数集合...

  • [IM] iOS 列表下拉加载更多,不跳跃

    效果 对,就是简单的下拉加载更多 实现 这里给出了答案 简单点就是,使用旋转CGAffineTransformMa...

  • 走近源码:Redis跳跃列表究竟怎么跳

    在前面介绍压缩列表ziplist的时候我们提到过,zset内部有两种存储结构,一种是ziplist,另一种是跳跃列...

  • 针对Redis数据库的造轮子计划(数据结构 - 1)

    Redis 数据库底层有‘简单动态字符串’、‘链表’、‘字典’、‘跳跃表’、‘整数集合’和‘压缩列表’这 6 种数...

  • Redis数据结构——链表

    前言 Redis链表为双向无环链表! Redis使用了简单动态字符串,链表、字典(散列表)、跳跃表、整数集合、压缩...

网友评论

    本文标题:跳跃列表

    本文链接:https://www.haomeiwen.com/subject/qwqdoftx.html