数据结构三——跳表

文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。

跳表的由来

说明:图片来自极客时间
由来
  二分查找的数据结构是数组,利用数组随机访问的特定查找的时间复杂度是O(logn)。如果数据结构是链表,可以达到这样的速度吗?答案是可以的。只是要改造。改造之后的结构就是跳表,是一种动态数据结构,可以支持快速的插入、删除、查找、按范围查找。功能类似于红黑树。Redis中的有序集合使用的就是跳表。

跳表的结构

数据结构三——跳表
  对于单链表来说,存储的数据是有序的,想要查找某个数,时间复杂度是O(n)。如果对单链表建一个一级“索引”,就是说每两个节点提取一个节点。提取出的节点有一个down指针指向原始链表中的同一个节点。
  数据结构三——跳表
  现在要查找数据16,那么查找路径是:1,4,7,9,13,13(原始链表),16。查找7个节点。单链表查找16需要查找10个节点。
  如果对一级索引再建“索引”,形成二级索引。
  数据结构三——跳表
  现在要查找数据16,那么查找路径是:1,7,13,13,13,16。查找6个节点。
  当n小的时候,减少的节点数量不明显。如果是n=64。建5级索引。
  数据结构三——跳表
  现在查找62需要11个节点,路径是1,33,33,49,49,57,57,61,61,61,62。原来需要62个节点。提升效果很明显。当n越大,提升效果越明显。
  跳表=链表+多级索引

跳表的时空复杂度

时间复杂度
 当节点个数为n的时候,跳表会建几层索引呢?第1级索引节点个数n2\dfrac{n}{2},第2级索引节点个数n4\dfrac{n}{4},第k级索引节点个数n2k\dfrac{n}{2^k}。最上面一层索引节点个数是2。也就是说2=n2l2=\dfrac{n}{2^l},l+1=log2nl+1=log_2n,l=log2n1l=log_2n-1。再加上原始链表层,跳表有log2nlog_2n层,记为logn。
 每一层最多查询的节点个数是3。因为在建每一层索引的时候,是每2个数据建一个节点。例如查找数据x,在第k层发现y<x,x>zy<x,x>z,所以通过y的down指针向,从第k层走到第k-1层。而y和z节点之间最多有3个节点(包含y和z)。
 数据结构三——跳表
 那么跳表的时间复杂度就是O(logn)。和二分查找是同样的查找效率。
空间复杂度
 链表的查找速度和二分一样,这是需要付出空间代价的。也就是以空间换时间。那么额外需要多少空间呢?n2+n4+n8+...+2=n\dfrac{n}{2}+\dfrac{n}{4}+\dfrac{n}{8}+...+2=n,等比数列求和。所以空间复杂度是O(n)。

跳表插入和删除

插入
 对于插入来讲,为了维持链表的有序性,在插入一个数据的时候需要先查找到插入的位置。
 数据结构三——跳表
 例如在链表中插入6,需要查找插入位置,查找节点1,1,4,4,5,时间复杂度和查找一个数字类似,O(logn)。链表的插入操作是O(1),所以整体插入操作时间复杂度O(logn)。
删除
 删除操作不仅要删除链表层,同时需要删除索引层的节点。时间复杂度O(logn)。

动态索引表更新

索引更新
 当不断插入数据的时候,如果不更新索引层,极端情况下跳表退化为单链表。当插入数据的时候,同时更新某些索引层。至于在哪些层建索引,可以通过随机函数来选择。

思考题

1 redist为什么使用跳表而不是红黑树?
 redist的核心操作是:
 插入一个数据;
 删除一个数据;
 查找一个数据;
 按范围查找一个区间内的数据;
 迭代输出有序序列
 红黑树效率不高的操作是:按范围查找一个区间内的数据。
 其他原因:跳表更容易实现,代码比较简单。

2 如果每3个或者5个节点抽取一个做索引,那么跳表的时间复杂度和空间复杂度是多少呢?
如果每三个或者五个节点提取一个节点作为上级索引,那么对应的查询数据时间复杂度,也还是 O(logn)。其实这里的底数已经不是2,而是3或者5。
空间复杂度,也依然是一个等比数列的和:n3+n9+n8+...+3=32(n1)\dfrac{n}{3}+\dfrac{n}{9}+\dfrac{n}{8}+...+3=\dfrac{3}{2}(n-1),记为O(n)。