1.什么是跳表-skiplist
skiplist 是由 William Pugh 发明的,最早出现于他在 1990 年发表的论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》。
skiplist,顾名思义,首先它是一个 list。实际上,它是在有序链表的基础上发展起来的。如果是一个有序的链表,查找数据的时间复杂度是 O(N)。
William Pugh 开始的优化思路:
假如我们每相邻两个节点升高一层,增加一个指针,让指针指向下下个节点,如下图 b 所示。这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半。由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了,需要比较的节点数大概只有原来的一半。
以此类推,我们可以在第二层新产生的链表上,继续为每相邻的两个节点升高一层,增加一个指针,从而产生第三层链表。如下图 c,这样搜索效率就进一步提高了。

如图 b 中,查找 8,从头结点出发,6 比 8 小,向右走,跳跃到 6;8 比 9 小,当前节点向下走,从下面指针出发,7 比 8 小,跳转到 7;8 比 9 小,向下走,走不了了,链表中不存在 8.
如图 c 中,查找 19,比 9 大,向右走,跳跃到 9;比 21 小,向下走,比 17 大,向右走,跳跃到 17,比 17 大,向右走,跳跃到 17,比 21 小,向下走,跟 19 相等,找到了。
skiplist 正是受这种多层链表的想法的启发而设计出来的。实际上,按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似二分查找,使得查找的时间复杂度可以降低到 O(log n)。但是这个结构在插入删除数据的时候有很大的问题,插入或者删除一个节点之后,就会打乱上下相邻两层链表上节点个数严格的 2:1 的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成 O(n)。
skiplist 的设计为了避免这种问题,做了一个大胆的处理,不再严格要求对应比例关系,而是插入一个节点的时候随机出一个层数。这样每次插入和删除都不需要考虑其他节点的层数,这样就好处理多了(按时插入删除,我们仍然需要考虑其他节点对应层指针的连接问题)。细节过程入下图:

2.skiplist 的效率如何保证?
上面我们说到,skiplist 插入一个节点时随机出一个层数,听起来怎么这么随意,如何保证搜索时的效率呢?
这里首先要细节分析的是这个随机层数是怎么来的。一般跳表会设计一个最大层数 maxLevel 的限制,其次会设置一个多增加一层的概率 p。那么计算这个随机层数的伪代码如下图:

在 Redis 的 skiplist 实现中,这两个参数的取值为:
p = 1/4
maxLevel =







