数据结构 | SkipList(跳表)

写在前面

该文并不是跳表的入门文章,而是致力于以简洁精炼的语言来描述 SkipList,来弥补上次面试时被问到跳表结果脑中只有图片没有文字的尴尬场景。。。



SkipList(跳表)

SkipList 是一种查找结构

结构

它的结构是一个有序链表,但是该链表的节点的具有多个指针,且不止指向下一个节点。在该链表上,每 N 个节点还会拥有一个指向后第 N 个节点的指针,N 值通常为 2 的幂。一个节点上可能具有多个 N 值指针,指针按照 N 值大小从高到低排序,分为多个"层"


查找

当要查找的时候:

  1. 头节点具有所有的 N 值的指针,头节点为当前节点
  2. 取出当前节点具有的最高的 N 值的指针
  3. 观察该指针指向的节点的值
  4. 如果等于目标值,说明节点找到,退出
  5. 如果大于目标值,将当前高度减一
    1. 若此时高度为零,说明值不存在,退出
    2. 否则,返回到 第 3 步
  6. 如果小于目标值,前进到该节点,返回到 第 2 步
  7. 当来到尾部时,代表值不存在,退出

在查找的时候,由于 N 值为 2 的幂,故每一次 高度减一 ,需要查找的节点就会减少一半,所以时间复杂度为 \(O(logN)\)

但如果这样,由于插入和删除时需要维护 N 值的正确性,时间复杂度可能退化为 \(O(N)\) 。所以改进为了插入时高度随机取值。

且为了保证层数越高越稀疏,该随机值不会随机分布,其计算过程如下:

  1. 起始高度为 1
  2. 下一层被创建的概率为 \(P\)
  3. 若未到达高度上限,且创建成功,则回到第二步
  4. 此时的高度值则为要插入节点的当前高度

插入

对于插入来讲,由于我们使用了随机高度的做法,所以不需要维护 N 值的正确性,故变得十分简单。

只需要先进行一次查找的过程,同时记录发生了高度下降的节点,在查找完成后根据高度,来从记录中更改引用关系。

时间复杂度为 \(O(logN)\)


删除

删除则也与插入同理,只不过更改引用关系时执行的是将前置节点与后置节点连接的操作。

但是需要这需要对所有高度进行查找,所以节点最好选用双向链表,可以减少复杂度。

时间复杂度为 \(O(logN)\)


与红黑树的对比

跳表的时间复杂度与红黑树相同,且都是用于在内存中的一种查找结构。

当然也有不同的地方,Redis 的开发者还给出了使用跳表的理由

There are a few reasons:

  1. They are not very memory intensive. It's up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees.

  2. A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.

  3. They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch (already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.


posted @ 2021-11-10 19:10  en_oc  阅读(322)  评论(3编辑  收藏  举报