跳表(SkipList) | 青训营笔记

139 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第7篇笔记

跳表的介绍

跳表全称为跳跃列表,它允许快速查询,插入和删除一个有序连续元素的数据链表。

跳跃列表的平均查找和插入时间复杂度都是O(logn)。

image.png

每个带有箭头的框表示一个指针, 而每行是一个稀疏子序列的链表;底部的编号框(黄色)表示有序的数据序列。查找从顶部最稀疏的子序列向下进行, 直至需要查找的元素在该层两个相邻的元素中间。

跳表的过程

跳表是由链表演变来的。对链表加多级索引的结构,就是链表。

假设每两个节点提取一个节点建立索引,当然也可以每三个节点或者四个 五个

我们在原始链表的基础上,每两个结点提取一个结点建立索引,我们把抽取出来的结点叫做索引层或者索引,down 表示指向原始链表结点的指针。

image.png

image.png

跳表的时间复杂度

假设每两个节点提取一个节点建立索引的话,m级索引的话,那么节点个数就是:

n>n/2>n/4>...>n/2mn -> n/2 -> n/4 -> ... -> n/2^m

假设m级索引,并且节点为2个,m=log(n)1m=log(n)-1。加上原始链表的话,那个整个跳表的高度就是log(n)。

假设每两个节点提取一个基点建立索引的话,每一级最多遍历2个节点。为什么呢?

因为我们是每两个结点提取一个结点建立索引,最高一级索引只有两个结点,然后下一层索引比上一层索引两个结点之间增加了一个结点,也就是上一层索引两结点的中值,二分查找,每次我们只需要判断要找的值在不在当前结点和下一个结点之间即可。

如上图所示,我们要查询红色结点,我们查询的路线即黄线表示出的路径查询,每一级最多遍历两个结点即可。

所以跳表的查询任意数据的时间复杂度为 O(2log(n))O(2*log(n)),前边的常数 2 可以忽略,为 O(log(n))O(log(n))

我们想要为跳表插入或者删除数据,我们首先需要找到插入或者删除的位置,然后执行插入或删除操作,前边我们已经知道了,跳表的查询的时间复杂度为 O(logn),因为找到位置之后插入和删除的时间复杂度很低,为 O(1),所以最终插入和删除的时间复杂度也为 O(longn)。

一般跳表可以和hash配合使用,因为hash有桶,占用的内存较大

跳表——空间换时间

跳表的效率比链表高了,但是跳表需要额外存储多级索引,所以需要的更多的内存空间。

跳表的空间复杂度分析并不难,如果一个链表有 n 个结点,如果每两个结点抽取出一个结点建立索引的话,那么第一级索引的结点数大约就是 n/2,第二级索引的结点数大约为 n/4,以此类推第 m 级索引的节点数大约为 n/(2^m),我们可以看出来这是一个等比数列。

这几级索引的结点总和就是 n/2+n/4+n/8…+8+4+2=n-2 ,所以跳表的空间复杂度为 o(n)。

那么我们有没有办法减少索引所占的内存空间呢?可以的,我们可以每三个结点抽取一个索引,或者没五个结点抽取一个索引。这样索引结点的数量减少了,所占的空间也就少了。