1 基本概念
1.1 概述
链表搜索的最坏时间复杂度是O(n),搜索过程中只能逐个遍历,不能能跳过结点。搜索平衡查找树,在一次比较后我们可以跳过半数的结点。而查找排好序的数组,可以通过二分搜索来访问元素。为了加快链表查找速度,因此有了跳跃表(Skip List)。思路很简单,通过创建多层结点,跳过部分结点来达到加快查找的目的。
以下图为例,为了加快查找速度我们增加了一层结点。当前最大层高为2。以搜索50为例,从第一个结点10开始, 50大于 10的下一个结点30,那么在第二层结点条约至30,而50小于30的下一个结点57。随后从30开始逐个遍历结点,直到搜索到50。这样就减少了搜索的结点数量。
如何进一步加快搜索?只要增加更多的跳跃点,那么就能跳过更多的结点加快查找速度。
1.1 完美跳跃表
为了过滤更多的结点,按照二分查找的思路,如果每次能过滤一半的结点,那么查找速度会极大提升。因此从最初的链表开始,每一段的中间点增加一个跳跃点。每一个子段也这样划分,每次就能舍弃一半的结点。这种具有每段都有一半结点性质的跳跃表称之为完美跳跃表(perfect skipList)
完美跳跃表查找的时间复杂度为O(logn),但进行插入和删除时为了保持每段有一半结点的这种性质需要对列表进行重排。为了保持原有性质,更新十分复杂。为了解决插入删除处理复杂的问题,需要做出一定权衡。
1.2 随机化跳跃表
我们并不保证每段一定包含一半的结点。当插入一个结点后,我们需要调整跳跃点的层高,我们利用抛硬币的方式处理,即一个只产生0/1的随机数生成器。当抛到正面且未达到最大跳跃点最大高度时,跳跃点的层高加一,因此跳跃点增加最大只会到最大高度。当遇到反面时层高不增加,执行插入。这样在进行插入操作时,处理就变的简单了。
1.3 确定高度
如1.2所述,跳跃表结点高度的生成可以按如下方式获取。
randomLevel()
lvl := 1
//random() that returns a random value in [0...1)
while random() < p and lvl < MaxLevel do
lvl := lvl + 1
return lvl
MaxLevel 是跳跃表中层高的上界。
计算公式为[3]:
n是列表中的结点数,p为是否继续增加层高的概率。
当列表中结点数量为65536, p = 1/2时,最大高度不小于16。
1.4 结点结构
一个结点由key和指针forward组成。forward包含了下一个对应位置的结点指针。
2 插入操作
插入操作
- 从结点最高层开始比较key确定所插入key所在的位置。如果插入key大于下一个结点的key,那么向前移动到下一个结点。
- 如果插入的key小于下一个结点的key,通过更新update[i]数组,保存当前当前结点i,接着移动到下一层搜索。当移动到第0层,最终将确定插入key的位置。
插入操作伪代码如下:
Insert(list, searchKey)
local update[0...MaxLevel+1]
x := list -> header
for i := list -> level downto 0 do
while x -> forward[i] -> key forward[i]
update[i] := x
x := x -> forward[0]
lvl := randomLevel()
if lvl > list -> level then
for i := list -> level + 1 to lvl do
update[i] := list -> header
list -> level := lvl
x := makeNode(lvl, searchKey, value)
for i := 0 to level do
x -> forward[i] := update[i] -> forward[i]
update[i] -> forward[i] := x
搜索过程中会发生update数组的更新,update数组记录的前一个结点的位置信息。
备注:上图update数组1位置似乎应该是9。
插入的平均时间复杂度为O(lgn),最坏时间复杂度为O(n)。
3 查找与删除
如下,我们希望删除结点6,操作步骤如下图:
首先需要找到结点,同时要对update数组更新,伪代码如下:
Delete(list, searchKey)
local update[0..MaxLevel+1]
x := list -> header
for i := list -> level downto 0 do
while x -> forward[i] -> key forward[i]
update[i] := x
x := x -> forward[0]
if x -> key = searchKey then
for i := 0 to list -> level do
if update[i] -> forward[i] ≠ x then break
update[i] -> forward[i] := x -> forward[i]
free(x)
while list -> level > 0 and list -> header -> forward[list -> level] = NIL do
list -> level := list -> level – 1
查找的主要流程如下图:
伪代码如下:
Search(list, searchKey)
x := list -> header
-- loop invariant: x -> key level downto 0 do
while x -> forward[i] -> key forward[i]
x := x -> forward[0]
if x -> key = searchKey then return x -> value
else return failure
查找与删除的时间复杂度一样。平均复杂度:O(lgn),最坏时间复杂度:O(n)
4 参考
1.跳跃表基本概念,https://www.geeksforgeeks.org/skip-list/
2.跳跃表,https://www.cs.cmu.edu/~ckingsf/bioinfo-lectures/skiplists.pdf
3.跳跃表,https://www.csee.umbc.edu/courses/undergraduate/341/fall01/Lectures/SkipLists/skip_lists/skip_lists.html