数据结构与算法——跳表

基本概念

二分查找利用静态数组随机访问的特性,可以实现在有序的数组中快速找到某个值,但是因为静态数组需要申请连续的内存空间,所以当数据规模比较大时,在内存中可能无法申请到所需的连续空间。因此,基于这一特性,我们考虑能否将二分查找应用于链表结构,这样就避免连续空间的限制,但是对于链表结构,怎样提高它的查找效率是解决该问题的关键。通过给链表建立多级索引的方式来对其进行改造,再与二分查找相结合就可以大大的提升查找效率这种改造的链表我们称之为跳表

理解跳表

如下图所示,我们每隔两个结点,选取一个结点作为索引层的元素,这样第一级索引层的结点个数,为n/2。假如,我们要找16,我们首先可以遍历索引层,当遍历到13时,检测它的下一个结点为17,所以确定16在它们之间,13结点通过down指针找到原始链表中13的位置,再继续向后查找找到16。可以对比一下如果没有建立索引,需要遍历10个结点,有了索引层之后只需遍历7个就可以找到。因此得出的结论是查找的效率变高了

上面的例子我们感觉查找提升的速度并不是很多,因此我们可以按照之前的思路,再建立一层索引,结果如下图所示。

 同样查找16,上图的方式仅需遍历6个结点。如果数据规模更大,该方法提升查找速度的效果会更加明显。

跳表的查询速度

我们都知道单链表中查询某个数据的时间复杂度是O(n)。现在我们来分析一下多级索引的跳表查询某个数据的时间复杂度。假设链表中有n个结点,如果按照每两个结点抽出一个结点作为上一级索引的结点,那么第一级索引的结点个数大约是n/2,第二级索引的结点个数大约就是n/4,第三级索引的结点个数大约就是n/8,依次类推,也就是说,第k级索引的结点个数是第k-1级索引的结点个数的1/2,那第k级索引结点的个数就是n/(2^k)。

假设索引有h级,最高级的索引有2个结点。通过上面的通项公式可有n/(2^h) = 2,从而求得h = logn - 1。如果包括原始的链表层,整个链表的高度就是logn。如果每层我们需要遍历m个结点,则在跳表中查询一个数据的时间复杂度就是O(m*logn)。分析可知,每层最多只需遍历3个结点,因此最终的时间复杂度为O(3logn)即O(logn)。

高效的动态插入和删除

因为跳表的查询时间复杂度是O(logn),而对于链表的插入而言,单纯的插入某一个数据的时间复杂对为O(1),因此我们可以知道在跳表中插入某一数据的时间复杂度也为O(logn)。具体插入某一数据的过程如下图所示

删除跳表中某一结点的某一数据时,与删除链表中的某一数据类似,只不过查找该数据的速度更快。还需要注意的如果原始链表为单链表,在删除某一结点时,需要找到其前驱结点,时间复杂度同样为O(logn)。

跳表索引的动态更新

当我们不停的往跳表中插入数据时,如果我们不更新索引,就有可能某2个索引结点之间的数据非常多的情况。极端情况下,跳表会退化为单链表。作为动态数据结构,我们需要某种手段来维护索引与原始链表大小之间的平衡,也就是说,如果链表中结点个数增加,索引结点就应该相应地增加一些,避免时间复杂度退化,以及查找、删除、插入操作性能下降。跳表是通过随机函数来维护前面提到的“平衡性”。通过随机函数,来决定将插入结点,可以插入到哪几级索引中,比如随机生成了值K,那我们就将这个结点添加到第一级到第K级这K级索引中。举个栗子,如下图所示

关于随机函数的选择,以及实现我还没有深入研究,后面研究明白了会继续和大家分享。未完待续……

参考链接:https://time.geekbang.org/column/article/42896#previewimg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值