定义
链表加多级索引的结构,称为跳表。
特点
单链表是有序的,然后提升多层索引,提高查询效率。
基于链表实现的二分查找。
每两个节点会抽出一个节点作为上一级索引的节点,第k级索引的个数,是第k-1级索引个数的1/2,那么第k级索引的个数就是n/2^k。
每两个节点会抽出一个节点作为上一级索引的节点,查询的时间复杂度为logn
是基于链表实现的二分查找,主要利用了空间换时间的思路。
因为每2个节点抽取一个节点提升为索引,所以每个节点最多访问3个元素。
空间复杂度
- 假设有n个节点的有序链表,每两个节点提取一个为索引,第一级索引为n/2,第二级为n/4,以此类推,每提升一级的个数,就是上一级索引的二分之一倍,相加起来为n/2+n/4+n/8+...+n/2^k+...+8+4+2=n-2,所以空间复杂度为O(n)。也就是说,将n个节点构成成为跳表,需要额外近似n个节点大小的空间存储索引。
- 降低空间复杂度的办法为,可以更多个节点抽取一个节点作为索引,如每3个节点,或者每5个节点抽取一个节点上升为索引,这样索引节点数减少,空间复杂度降低。如每3个节点抽取一个节点作为索引,那么索引个数为n/3+n/9+n/18+...+n/3^2+...+18+9+3=n/2,虽然空间复杂度为O(n),但索引节点总个数为n/2,降低为原链表的一半,所以需要的额外空间更少了。
- 实际开发过程中,原始链表节点存储的可能是很大的对象,而索引存储的关键值或几个指针,所以索引节点的大小比原始链表小的多,所以索引节点大小和占用的内存我们都可以忽略不计。
跳表操作
高效的插入和删除
- 插入:单向链表的插入本身时间复杂度就是O(1),所以通过跳表索引,以O(logn)的时间复杂度定位到要插入的节点,然后插入数据,非常高效。
- 删除:利用跳表O(logn)定位到要删除的节点,因
- 为删除必须要定位到前驱节点,所以比插入要多遍历一次区间节点。当然如果我们要是用双向链表,就不用考虑寻找前驱节点的问题了。
索引的更新
当频繁的插入数据,区间节点内节点数量增大,当插入不均匀时,可能导致一个区间内节点特别多,这样导致此节点退化成单向链表,复杂度增大,所以我们要及时的更新索引,避免复杂度退化。
更新索引的策略有使用随机函数,提升增加的节点,升级为索引。随机函数选择很有讲究,从概率上讲,要能够保证跳表索引大小和数据平衡,减少复杂度退化。
应用
redis的有序集合就是跳表实现。
- 为什么选择跳表而非红黑树,通过redis使用手册查看有序集合的操作有:
- 插入一个数据。
- 删除一个数据。
- 查找一个数据。
- 按区间查找数据。
- 迭代输出有序序列。
其中,插入,删除,查找,迭代输出这几个操作,红黑树也可以实现,且复杂度跟跳表一样都是O(logn),但是按区间查找,跳表比较优越,跳表在根据索引找到区间里对应的起始点后,顺序往后输出就可以,这样非常高效,而红黑树不行。
- 跳表的代码的可读性比较好,比红黑树容易。
- 跳表可以通过改变构建索引策略,来有效平衡执行效率和内存消耗,这点很灵活。
代码实现
todo