跳表的简单介绍

跳表的简单介绍

  • 前言
  • 链表是啥
  • 如何解决链表痛点
  • 实现分析
    • 插入操作
    • 删除操作
    • 查询操作
  • 总结一下

前言

前两天看system design的东西,突然就看到了跳表skip list,然后我就不是很懂了,这次咱们把它搞清楚,确实是非常好理解的东西。

链表是啥

List大家肯定都很熟悉,但是List的查找操作其实是很僵硬的,对吧?O(n)的复杂度没跑了。

如何解决链表痛点

这就是为啥咱们要用跳表了。跳表其实在很多数据库的线性结构中都在使用,比如大名鼎鼎的redis(我对redis的了解就很肤浅)。各位试想,如果我能事先把这个List的某一些关键节点给他拎出来,在做查询操作的时候能先按照这个上层的关键节点链表先查询,然后再根据范围匹配,进入下一层进行细致查询,不就能跳过很多没用的范围了嘛!两层不够的话你还可以搞多层啊!
跳表的简单介绍_第1张图片
上面这张图描述的是跳表的结构,这里我引用了《小灰-算法漫画?》的图,侵删。
那么如何提取高层的索引呢?其实很简单,就搞个随机,每个节点都有概率被提升上去。具体的插入删除操作,咱们代码实现。
跳表的简单介绍_第2张图片
这张图就很清楚的表达了一个跳表的组织逻辑。
1.首先每一层都是一个双向链表List,且都有最小值和最大值(其实就是为了我们插入方便,挺好的)
2.每个节点元素都有四个指针,当然除了本层的两个以外,还有两个分别指向上一层和下一层。

实现分析

咱们这次不写代码了,感觉这个跳表写起来还是很简单的。
咱们这次不用List,就自己搞一套链表的东西,上下左右四个指针,外加一个val参数。
当然了,除了节点的描述外,跳表类还要有个全局的跳表对象,这是入口。

插入操作

对于插入操作,自然是先从第一层开始横向展开,然后找到合适的位置后,下去到下一层,同时把这一层的下去的节点入栈(放心后面会用到的)。一直深入到最后一层,然后选择插入节点。
OK在最后一层咱们得开始上去了。先抛个硬币(rand一下)决定是否上去,如果不上去就直接完成,如果上去就搞一下。刚刚咱们的入栈就很有必要了。这里其实还有个问题就是,如果上去的时候自己成为了新的第一层,咋办?
这是两个问题,第一个问题是,如何判断自己成为了第一层?很简单,刚刚咱们的栈就派上用场了,可以判断一下栈里还剩多少元素。第二个问题是,如何处理新的第一层呢?很简单,就开辟一个新的元素行(当然只有自己+INT_MIN+INT_MAX),然后和第二层的关系搞一下,最后更新一下跳表的入口即可。
对于重复元素的插入,我个人建议是插入到这个重复元素的后面,这样可以尽可能的不破坏其有序性质。

删除操作

这就更简单了,首先咱们从入口出发,开始找这个元素,直到在最底层找到这个元素。找不到的话就直接返回。
找到以后,定位到上一层,然后直接把下一层删掉。
就这么一直上去,直到上一层没人了。于是咱们就得判断一下,左右节点是INT_MIN和INT_MAX,还是其他。如果是前者,那就全部删除,但是记得先更新一下跳表的入口地址。
我个人感觉,没必要先找下去,直接从上到下删除也挺好的,所以我更正一下。

查询操作

这个就很简单了,看看上一步的错误的查询操作即可。

总结一下

跳表对线性表做了非常大的优化,在插入删除等操作上时间复杂度直接就成了log级别了,当然付出的代价也显而易见,直接就存储了很多冗余的信息。
Redis选择使用跳表而不是红黑树来实现有序集合。这个咱们怎么理解呢?很明显,对于有些插入删除操作而言,红黑树和跳表都挺不错的,但是咱们想想看看链表的优势就知道了,如果你想搞个范围查找,那么红黑树就直接爆炸了。

你可能感兴趣的:(算法与数据结构)