跳跃列表

一、业务场景

某游戏开发公司,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+树的知识,将在以后的章节中提到。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 5,636评论 0 13
  • Java继承关系初始化顺序 父类的静态变量-->父类的静态代码块-->子类的静态变量-->子类的静态代码快-->父...
    第六象限阅读 2,136评论 0 9
  • Redis 是一个键值对数据库(key-value DB),数据库的值可以是字符串、集合、列表等多种类型的对象,而...
    吴昂_ff2d阅读 3,042评论 0 5
  • 每个人的家乡都有很多难忘的东西,他对你肯定有很多难忘的回忆。 在我的老家里有一颗枣树,这颗枣树给我带...
    开心果_14df阅读 169评论 0 0
  • 明天要完成的任务: 01.PPT制作 02.大纲 03.下午迎接检查 04.上学期后续工作
    羽佳成长故事阅读 127评论 0 0