数据结构与算法之美 | 学习笔记13 —— 跳表

对于链表不能采用二分查找方法,这时我们将链表稍加改造为跳表(Skip list),就可支持类似“二分”的查找算法。

一、跳表

对原始链表建立多级索引结构,每两个结点提取一个结点到上面一层级,将抽出来的这个层级叫索引或索引层。图中down表示down指针,指向下一结点。查找时,先从顶层的索引层遍历,通过down指针下降层级,直到原始链表。
一层索引
两层索引

五层索引

时间复杂度

如果链表里有n个结点,每两个结点抽出一个作为上一级索引的结点,那么第一级索引的结点个数为 n 2 \frac n2 2n,第二层为 n 4 \frac n4 4n,依次类推,第k级索引结点个数为 n 2 k \frac {n}{2^k} 2kn,假设索引共有h级,最高级有2个结点,则 n 2 h = 2 \frac {n}{2^h}=2 2hn=2 h = l o g 2 n − 1 h=log_2n-1 h=log2n1,加上原始链表这一层,整个跳表的高度即为 l o g 2 n log_2n log2n。查询某个数据时,如果每一层都遍历m个结点,那么在跳表中查询一个数据的时间复杂度为 O ( m l o g n ) O(mlogn) O(mlogn)。按照上图的索引结构,因为每两个结点提取一个索引结点,所以查询时每一级最多遍历3个结点,因此跳表中查询任意数据的时间复杂度为 O ( l o g n ) O(logn) O(logn)

空间复杂度

每层索引的结点数为:
每层索引结点数
等比数列相加为n-2。(注意此处项数为 l o g 2 n 2 log_2\frac n2 log22n),空间复杂度为 O ( n ) O(n) O(n)
如果每3个结点抽一个,那就是n/2。比两个结点的少了一半的存储空间。实际开发中,原始链表中存储的可能是很大的对象,而索引结点只要存储关键值和指针,并不需要存储对象,所以索引占用的额外空间可以就可以忽略了。

二、跳表的操作

跳表的动态插入和删除操作时间复杂度为 O ( l o g n ) O(logn) O(logn)

1. 插入和删除

确定好要插入的位置后,插入操作的时间复杂度为 O ( 1 ) O(1) O(1)。而确定位置的过程就是查找过程,因此整个插入操作的时间复杂度为 O ( l o g n ) O(logn) O(logn)
跳表的插入
删除的操作中,除了要删除原始链表中的结点,如果索引中出现,还要删除索引中的。当然,在删除操作时,对于单链表要获取前驱结点。

2. 跳表的动态更新

当频繁插入操作时,可能存在两个索引结点之间数据过多的情况,此时极端情况下可能会退化成单链表。
频繁插入退化成单链表
跳表通过随机函数来维护这种平衡性,往跳表中插入数据时,通过一个随机函数来决定结点插入到哪一级索引中,例如下图,就将插入数据同时建立第二层索引结点:
随机函数决定
代码实现:王争的GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值