其他分享
首页 > 其他分享> > 跳表

跳表

作者:互联网

跳表其实就是对链表进行改造,使链表能够进行类似于二分查找的算法。

 

我们知道,链表虽然内存消耗低,但是查询效率很低,想要去提高这个查询效率,我们可以参考下数组,数组为什么查询快?因为它是直接使用的偏移公式,按下标读取。那么我们可以参考这个思路,每隔一些结点就标记一下,比如每隔两个,这样类似于二分,把这些被标记的结点充当索引,那么这些被标记的结点组成的链表就叫做索引层或者叫索引。每个索引用down指针指向它在原始链表中对应的结点。

 

这样弄出一个类似索引的东西后,我们在查询时,只需要先在索引层定位到索引,然后再去原始链表查询,那么就能减少需要遍历结点。

如果我们还想更快,那么继续加索引就可以了

 

 

可以看到,索引级别越多,跳过的结点会越多,会展现出很明显的跳跃效果,所以跳表才叫做跳表。

我们一句话描述跳表:一种链表+多级索引的数据结构

 

跳表的查询速度到底有多快?

以上面的跳表为例,跳表是由链表加多级索引组成的,时间复杂度很明显就是每级索引的时间复杂度加上链表的时间复杂度,而索引有多少级取决于结点的多少,每级索引的查询时间复杂度取决于每级遍历索引结点的多少,链表的查询复杂度由于有索引的存在所以是O(1)或者O(2),即就是O(1)。所以我们下面要来求索引要多少级和每级索引的查询时间复杂度。

 

设链表的结点总数为n,我们以每两个结点取一个索引的话,那么第一级索引的结点个数大约就是 n/2,第二级索引的结点个数大约就是 n/4,第三级索引的结点个数大约就是 n/8,依次类推,也就是说,第 k 级索引的结点个数是第 k-1 级索引的结点个数的 1/2,那第 k级索引结点的个数就是 n/(2^k)。那么k怎么算?

由于我们作为例子的跳表是二分的,所以最顶级索引必定是不能再进行二分,也就是只能有两个,那么最顶级的结点个数:n/(2 ^ k) == 2,那么此时h == k == logn - 1。

 

接下来我们再求每级索引的查询时间复杂度,也就是每级索引会遍历多少结点,事实上,你会发现,每级的索引的范围都会上一级索引框定了,比如,一级索引的1,4,7索引,这三个索引被框定在二级索引的1,7之间,也就是说,每一层索引,你只能在被框定的三个结点中遍历,那么也就是每层索引最多遍历三个结点。

 

这有点像递归没有归,这样每层经历的索引数量相当于一个1,2,3中的一个,那么就是常数,所以我们最后得出所有索引的时间复杂度是O(h) == O(logn - 1),整个跳表的时间复杂度的话,还要加上链表,那么就是O(logn),这个时间复杂度和二分查找一毛一样,所以我们相当于在链表上实现了一个二分查找的算法。

 

跳表需要消耗多少额外的内存?

这个额外内存其实就是所有索引占的内存,而每级索引的数量看起来是约等于一个等比数列,所以额外内存造成的空间复杂度是O(等比数列的和)。

实际上,在软件开发中,我们不必太在意索引占用的额外空间。在讲数据结构和算法时,我们习惯性地把要处理的数据看成整数,但是在实际的软件开发中,原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值和几个指针,并不需要存储对象,所以当对象比索引结点大很多时,那索引占用的额外空间就可以忽略了。

 

跳表索引的动态更新

跳表依赖链表,而链表是可以动态收缩或者扩容的,当我们扩容时,要及时更新索引,否则就会出现某一级索引框定的下一级索引范围内有超过应有的结点数量的情况,比如上面的本来最多三个结点,没及时更新索引的话就可能变成了四个,五个等等。

 

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

 

 

标签:结点,每级,复杂度,链表,索引,跳表
来源: https://www.cnblogs.com/codemelo/p/16200198.html