暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

浅析skiplist(跳表)

开发架构二三事 2019-06-11
370

skiplist(跳表)的应用场景

1. 几种常用的数据结构

在学习跳表之前,我们先来比较下数组、链表、二叉查找树、平衡二叉树的一些比较:

  • 数组:支持随机访问,在有序情况下可以采用二分查找(binarySearch)来快速查找。缺点也很明显:在有序数组中插入和删除数据时,为了保持元素的有序性,需要进行大量的数据移动操作。

  • 链表:维护前后节点的指针,有序情况下,插入和删除数据操作比较简单。缺点就是没有办法快速查找,二分查找并不适用。

  • 二叉查找树:支持高效的二分查找,也能快速地进行插入和删除操作。缺点是在某些极端情况下,二叉查找树可能变成一个线性链表。

  • 平衡二叉树:对二叉树的缺点进行了改进,引进了平衡的概念。根据平衡算法的不同,具体实现有AVL树/B树(B-Tree)/B+树(B+Tree)/红黑树等。但是平衡二叉树实现起来比较复杂,较难理解。

2. 跳表

1. 结构

跳表由William Pugh 1989年发明。他在论文《Skip lists: a probabilistic alternative to balanced trees》中详细介绍了跳表的数据结构和插入删除等操作。论文是这么介绍跳表的:

  1. Skip lists are a data structure that can be used in place of balanced trees.

  2. Skip lists use probabilistic balancing rather than strictly enforced balancing and as a result the algorithms for insertion and deletion in skip lists are much simpler and significantly faster than equivalent algorithms for balanced trees.

复制

引用自:https://en.wikipedia.org/wiki/Skip_list

  • 跳表是一个“概率型”的数据结构,在很多应用场景中可以替代平衡树。与平衡树相比,有相似的渐进期望时间边界,但是它更快,更简单也更省空间。

  • 是一个分层结构的多级链表,最下层链表包括所有数据,每个层级都是下一层级的索引,是一个用空间换时间的方案:

2. 复杂度

  • 红黑树:插入、查找为O(logn),但常数项较小;无锁实现的复杂性很高,一般需要加锁;数据天然有序。

  • SkipList:插入、查找为O(logn),但常数项比红黑树要大;底层结构为链表,可无锁实现;数据天然有序。

skiplist性能:

场景平均值最坏情况
查找O(logn)O(n)
插入O(logn)O(n)
删除O(logn)O(n)

目前redis的zset和levelDB存储结构采用的就是skiplist。

3. 跳表特性

一个普通的有序链表:

如果从上面的列表中查找23需要遍历4次,查找59时需要遍历6次。而对这个链表,我们没法使用二分查找。

于是我们对数据节点加上一级索引如下图:

这样通过一级索引去找就相对来说快上很多。

还可以加上二级索引,如下图:

跳表结构:

  • 跳跃表的层数,我们称之为维度,从上到下,我们称之为降维,它由很多个维度维成。

  • 每一层都是一个有序的链表。

  • 每一层中相同的元素,我们称为“同位素”。

  • Skip List主要思想是将链表与二分查找相结合,它维护了一个多层级的链表结构(用空间换取时间),可以把Skip List看作一个含有多个行的链表集合,每一行就是一条链表,这样的一行链表被称为一层,每一层都是下一层的"快速通道",即如果x层和y层都含有元素a,那么x层的a会与y层的a相互连接(垂直)。最底层的链表是含有所有节点的普通序列,而越接近顶层的链表,含有的节点则越少。

  • 最底层的链表,即包含了所有元素节点的链表是L1层,或称基础层。除此以外的所有链表层都称为跳跃层。

  • 基础层包括所有的元素。

  • 对一个目标元素的搜索会从顶层链表的头部元素开始,然后遍历该链表,直到找到元素大于或等于目标元素的节点,如果当前元素正好等于目标,那么就直接返回它。如果当前元素小于目标元素,那么就垂直下降到下一层继续搜索,如果当前元素大于目标或到达链表尾部,则移动到前一个节点的位置,然后垂直下降到下一层。

  • Skip List还有一个明显的特征,即它是一个不准确的概率性结构,这是因为Skip List在决定是否将节点冗余复制到上一层的时候(而在到达或超过顶层时,需要构建新的顶层)依赖于一个概率函数,举个栗子,我们使用一个最简单的概率函数:丢硬币,即概率P为0.5,那么依赖于该概率函数实现的Skip List会不断地"丢硬币",如果硬币为正面就将节点复制到上一层,直到硬币为反。

  • 相比于二叉查找树,跳表维持结构平衡的成本比较低,完全靠随机。而二叉查找树需要Rebalance来重新调整平衡的结构。

  • 删除时自上而下,查找第一次出现节点的索引,并逐层找到每一层对应的节点,删除每一层查找到的节点,如果该层只剩下1个节点,删除整个一层(原链表除外)。

4. java实现

存储结构一般为:

jdk中提供的实现主要有:ConcurrentSkipListMap与ConcurrentSkipListSet,其中后者是基于前者实现的。关于这两个的源码分析,请关注之后的文章。

5. 参考

https://en.wikipedia.org/wiki/Skip_list


文章转载自开发架构二三事,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论