Redis-高级篇-skiplist

redis-skiplist

为什么引出跳表

  • 先从一个单链表来说 对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率就会很低,时间复杂度会很高O(N)

  • 痛点

    解决方法:升维,也即空间换时间

  • 优化

  • 案例:画一个包含64个节点的链表,按前面思路,建立五级索引

是什么

跳表是可以实现二分查找的有序链表

skiplist是一种以空间换取时间的结构。由于链表,无法进行二分查找,因此借鉴数据库索引的思想,提取出链表中关键节点(索引),先在关键节点上查找,再进入下层链表查找,提取多层关键节点,就形成了跳跃表。

但是,由于索引也要占据一定空间的,所以,索引添加的越多,空间占用的越多

总结来说,跳表=链表+多级索引

跳表时间和空间复杂度介绍

  1. 跳表的时间复杂度,O(logN)

    跳表查询的时间复杂度分析,如果链表里有N个结点,会有多少级索引呢?

    按照我们前面笔记,两两取首。每两个结点会抽出一个结点作为上一级索引的结点,以此估算:

    第一级索引的结点个数大约就是n/2

    第二级索引的结点个数大约就是n/4.

    第三级索引的结点个数大约就是n/8,依次类推......

    也就是说,第k级索引的结点个数是第k-1级索引的结点个数的1/2,那第k级索引结点的个数就是n/(2^k)

    假设索引有h级,最高级的索引有2个结点。通过上面的公式,

    我们可以得到n/(2^h)=2,从而求得h=log2n-1(log以2为底,n的对数)

    如果包含原始链表这一层,整个跳表的高度就是log2n(log以2为底,n的对数)

  2. 跳表空间复杂度

    跳表查询的空间复杂度分析

    比起单纯的单链表,跳表需要存储多级索引,肯定要消耗更多的存储空间。那到底需要消耗多少额外的存储空间呢?

    我们来分析一下跳表的空间复杂度。

    第一步:首先原始链表长度为n,

    第二步:两两取首,每层索引的结点数: n/2,n/4,n/8 ...,8,4,2 每上升一级就减少一半,直到剩下2个结点,以此类推,如果我们把每层索引的结点数写出来,就是一个等比数列。

    这几级索引的结点总和就是n/2+n/4+/8.+8+4+2=n-2。所以,跳表的空间复杂度是O(n)。也就是说,如果将包含n个结点的单链表构造成跳表,我们需要额外再用接近n个结点的存储空间。

    同理,若三三取首,每层索引的节点数: n/3, n/9, n/27, ..., 9, 3, 1,以此类推:

    第一级索引的结点个数大约就是n/3,第二级索引的节点个数大约就是n/9,第三级索引的结点个数大约就是n/27,每往上一级,索引节点个数都除以3,。为了方便计算,我们吧最高一级的索引节点个数视为1.我们把每级索引节点个数写出来,就是一个等比数列。

    通过等比数列求和公式,总的索引大概是n/3+n/9+n/27+...+9+3+1=n/2。所以,三三取首的跳表的空间复杂度也是O(n),但是比上面两两取首的跳表,节省了一半的索引空间。

    一般来说使用两两取首和三三取首就已经足够了,不需要再增加了。因为,如果我们继续增加,比如四四取首,五五取首,那空间复杂度就会越来越大,跳表的优势就没有那么明显了。

优缺点

优点:

跳表是一个最典型的空间换时间解决方案,而且只有在数据量较大的情况下才能体现出来优势。而且应该是读多写少的情况下才能使用,所以它的适用范围应该还是比较有限的

缺点:

维护成本相对要高, 在单链表中,一旦定位好要插入的位置,插入结点的时间复杂度是很低的,就是O(1)but 新增或者删除时需要把所有索引都更新一遍,为了保证原始链表中数据的有序性,我们需要先找到要动作的位置,这个查找操作就会比较耗时最后在新增和删除的过程中的更新,时间复杂度也是o(log n)


Redis-高级篇-skiplist
https://gstarmin.github.io/2023/09/11/Redis-高级篇-skiplist/
作者
Starmin
发布于
2023年9月11日
许可协议