跳跃表学习笔记
跳跃表(Skip List),是一种基于概率,提供了查找、插入、删除近似于O(logN)性能的数据结构。
这篇文章是学习跳跃表的一个记录,包含了跳跃表的原理以及其Java实现
介绍
跳跃表(Skip List),是一种基于概率,提供了查找、插入、删除近似于O(logN)性能的数据结构。
在线性数据结构中,查找、插入、删除性能均为O(N),当数据量比较大的时候,是非常不乐观的,因此,科学家们相处了各种各样的数据结构来进行优化,将线性的数据结构进行一系列转换,从而得到在理想情况下查找、插入、删除性能均为或近似为O(logN)的数据结构,前面提到的AVL、红黑树是通过平衡(旋转、变色)等将线性数据结构转换为平衡树,从而利用平衡树的特性来实现O(logN)的复杂度
除了平衡树之外,还有另外一种数据结构,跳跃表,采取另类的思路来实现同样的性能优化,平衡树等牺牲的是部分的操作性能,而跳跃表牺牲的则是空间
跳跃表可以达到与红黑树等类似的性能,并且实现起来相对简单,虽然消耗部分空间,但也具有一定的应用场景,例如redis中的sorted set、Java中的ConcurrentSkipListMap就是通过跳跃表来实现的
原理
跳跃表本质上是基于链表,并且跳跃表中的元素是有序的(默认根据key递减)
传统的链表查找、插入、删除操作都是O(N),跳跃表对其进行了优化,为原来的链表建立多个层次(基于概率函数)的索引,每个层次的索引依次递减(通过概率来控制,而非严格的数量关系),从而建立了一个基于链表的性能卓越的跳跃表数据结构,如下图所示:
最底层黄色的节点是实际上真正的数据节点,从最底层往上的每一个层次都是下一个层次的索引
查找节点
在查找的时候,从最head节点出发,在最顶层开始往右边查找,直到第一个不小于目标值的节点出现,说明目标节点位于当前节点区间,从上图中也可以看到,最顶层的索引是非常稀疏的,所以最上层节点的查找速度是非常快的,然后从当前节点往下一层次查找,重复上述步骤,直到到达最底层
举个栗子:查找节点8
1 | 1. head的最顶层(第4层)出发,此时已经到达第四层的末尾处 |
可以看到,此时仅仅需要4次查找,而链表本身的长度为10
插入节点
插入节点的前部分操作与查找节点一样,先找到离目标节点最近的节点(目标节点或前一个节点),然后插入目标节点,根据概率函数,计算当前节点应该所占有的层次,然后依次补充将每个层次对应的索引链表连接起来(普通的链表插入操作)
具体插入步骤参见上图
删除节点
删除节点的操作步骤与插入节点类似,只是现在是找到之后,将其从各个层次的索引链表中删除(普通的链表删除操作)
注:以上所有的图片均来自维基百科-跳跃表介绍
实现
从上面的描述以及图片示例,大致上已经能够猜测出实现的代码结构了
ListNode
1 | static class ListNode { |
SimpleSkipList
1 | /** |
get:获取元素
1 | public ListNode get(String key) { |
put:添加元素
1 | public void put(String key, String value) { |
remove
1 | public ListNode remove(String key) { |