1、跳跃链表
用某种数据结构来维护一组有序的数据的列表,尽可能在插入、删除、查找等操作上能够尽可能的快速。
- 数组:使用数组存储的话,采用二分法可以在 O(logn) 的时间里找到指定的元素,在进行插入和删除则时间复杂度为 O(n)
- 链表:使用链表存储的话,就插入、删除动作而言,所需的时间复杂度为 O(1) ,加上查找所需的时间复杂度为O(n),故插入、删除所需的总的时间复杂度为O(n)。
跳跃链表在有序链表的基础上进行了扩展(它具有二分查找的功能),查找特定值的时间复杂度为O(logn),单插入和删除时间复杂度为O(1),根据时间复杂度加法原则,增删时需要先定位到某位置,所以要加上查询所需O(lngn),所以跳跃链表在查询、插入、删除的时间复杂度为O(lng)。他是一种可以代替平衡树的数据结构。B+树结构的思想和跳跃链表同理。最底层存储数据,将特定数据提取到上一层作为索引。
小结:跳表的特点:
(1) 由很多层结构组成
(2) 每一层都是一个有序的链表
(3) 最底层的链表包含所有元素
(4) 如果一个元素出现x层,则x下的一层也会出现。
(5) 每个节点包含两个指针,一个指向同一层的下一个元素,一个指向下面一层的元素。
/**
*跳跃链表
*/
public class SkipListNode {
//元素
private int data;
//同一链表中的下一个元素指针
private SkipListNode next;
//指向下面一层的元素指针
private SkipListNode downNext;
}
PS:包括前面的数据结构,很多时候看一个类的成员变量,便能从宏观上把握这个类的作用。因为无论方法再多,都是再维护这些变量。
2、跳表的增删改查操作
(1)插入节点
插入结点的操作,最底层数据节点会增加,底层往上的各层索引节点会逐渐减少。这时候需要决定插入的节点是否需要添加到各层中作为索引。解决方式:抛硬币法
【抛硬币法】 也就是随机决定新节点是否提拔,每次向上提拔一层的几率是50%
- 因为跳跃表删除和添加的节点是不可预测的,很难用一种有效的算法来保证跳表的索引部分始终均匀。
- 随机抛硬币的方法可以让索引部分大体趋于均匀
//抛硬币:
int random_level()
{
K = 1;
while (random(0,1))
K++;
return K;
}
(2)删除节点:
- 只要在【索引层】找到要删除的节点,然后一次查找每一层删除相同节点。
- 如果某一层索引在删除后只剩下一个节点,那么整个一层就可以干掉了。
(3)查询节点:介绍跳表的图中已说明
3、跳表的应用
ConcurrentSkipListMap
ConcurrentSkipListSet
Redis sorted set的内部使用HashMap和跳跃表(SkipList)
后续会逐一介绍
4、跳表与其他数据结构的比较
- 跳跃表 vs 二叉查找树
二叉查找树的插入、删除、查找也是近似 O(logn) 的时间复杂度。 不过,二叉查找树是有可能偏向一方,将退化成O(n)的链表
- 跳跃表 vs 红黑树
红黑在查找,插入,删除也是近似O(logn)的时间复杂度,通过旋转(改变了原本数据结构)达到近似平衡,比起跳跃表直接通过一个随机数来决定跨越几层要复杂得多
java提供了工业级的红黑树TreeMap,但是链表并没有,需要自己手动实现。