跳跃表SkipList的基本概念

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 插入操作

插入操作

  1. 从结点最高层开始比较key确定所插入key所在的位置。如果插入key大于下一个结点的key,那么向前移动到下一个结点。
  2. 如果插入的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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值