美文网首页
《数据结构与算法之美》13——跳表

《数据结构与算法之美》13——跳表

作者: 大杂草 | 来源:发表于2020-07-21 17:48 被阅读0次

上篇文章介绍了二分查找算法。因为二分查找底层依赖的是数组随机访问的特性,所以只能用数组来实现。那么数据存储在链表中,就不能用二分查找算法吗?

对于一个单链表,即便链表中存储的数据是有序的,如果要查找某个数据,也只能从头到尾遍历链表。这样查找效率很低,时间复杂度是O(n)。

原始链表

如果像图中那样,对链表建立一级“索引”,查找起来是不是就能更快些呢?每两个结点提取一个结点到上一级,把抽出来的那一级叫作索引或索引层

索引层1

如果要查找某个结点,比如16。原来单链表的查找要遍历10个结点,而使用索引后,只需要遍历7个结点。可以看出,加了一层索引之后,查找一个结点需要遍历的结点个数减少了,也就是说查找效率提高了。

如果我们在第一级索引的基础上,建第二级索引。现在再来查找16,需要遍历的结点数量(6)又减少了。

索引层2

前面讲的这种链表加多级索引的结构,就是跳表。

跳表查询有多快

算法的执行效率可以时间复杂度来度量。我们知道,在一个单链表中查询某个数据的时间复杂度是O(n)。那在一个具有多级跳表中,查询某个数据的时间复杂度是多少呢?

假设链表里有n个结点。按照每两个结点抽出一个结点作为上一级的结点,那第一级索引的结点个数大约是n/2,第二级索引结点个数大约是n/4,以此类推,第k级索引的结点个数是第k-1级索引的结点个数的1/2,那么第k级索引结点个数是n/(2^k)。

假设索引有h级,最高级的索引有2个结点,通过上面的公式,可以得到n/(2^h)=2,从而求得h=log2(n-1)。如果包含原始链表这一层,整个跳表的高度就是log2(n)。

在跳表中查询某个数据的时候,如果每一层都要遍历m个结点,那跳表中查询一个数据的时间复杂度就是O(m*logn)。

按照前面这种索引结构(每两个结点抽出一个结点作为上一级的结点),每一级索引最多只需要遍历3个结点,即m=3。

假设要查找的数据是x,在第k级索引中,遍历到y结点之后,发现x>y,x<z,就通过y结点的down指针下降到第k-1级索引。在第k-1级索引中,y和z之间只有3个结点(包含y和z),以此类推,每一级索引都最多只需要遍历3个结点。

查找过程

所以跳表中查询任意数据的时间复杂度是O(logn)。

跳表是不是很浪费内存

比起单链表,跳表要存储多级索引,相对要消耗更多的存储空间。

假设原始链表大小是n,那第一级索引大约是n/2,第二级索引大约是n/4,以此类推,直到第k层索引剩下2个结点。

每层索引的结点数

这是一个等比数列,索引总和是n/2+n/4+n/8+....+8+4+2=n-2。所以跳表的空间复杂度是O(n)。

高效的动态插入和删除

我们知道,在单链表中,一旦定位好要插入的位置,插入结点的时间复杂度是很低的,就是O(1)。但定位位置这个查找操作就会比较耗时。

相对单链表的查找的时间复杂度O(n),跳表的查找的时间复杂度是O(logn)。

动态插入

删除操作跟插入类似,但如果这个结点在索引中也有出现,那还要删除索引中的。

跳表索引动态更新

当不停往跳表中插入数据时,如果不更新索引,那可能出现某2个索引结点之间数据非常多的情况。极端情况下,跳表会退化成单链表。

跳表索引动态更新1

可以通过一个随机函数,来决定将这个结点插入到哪几级索引中,比如随机函数生成了值K,那就将这个结点添加到第一级到第K级这个K级索引中。

跳表索引动态更新2

代码实现

示例代码

相关文章

网友评论

      本文标题:《数据结构与算法之美》13——跳表

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