跳跃列表,平衡树的概率替代方法。平衡树算法在执行操作时重新排列树,以保持一定的平衡条件并保证良好的性能。跳跃列表是使用概率平衡而不是严格强制平衡的数据结构。因此,在跳跃列表中插入和删除的算法要比平衡树的等效算法简单得多,而且要快得多
跳跃表介绍
跳跃表的每个节点包含一个高度k值,而每层高度都有一个指针,指向后一个节点,形成这层高度的有序的单链表。实际中,每个节点的这个高度k值是随机的,并且永远不会被修改。表现为一种带有跳过中间节点的额外指针的链表,即跳跃链表
在单链表中搜索时,我们可能需要检查列表的每个节点(下图)。
我们要查询值为12的节点,那么我们需要从head节点开始,依次向后对比,需要检查4次
而如果将该有序链表改为如下结构
从head节点开始,每隔2个节点的节点都增加一个指针,分别指向同高度的下一个节点,那么检查的节点数不超过
n/2 + 1个
我们要查询值为12的节点,那么我们需要从head节点开始,同时从上至下,依次向后对比。检查路径是head->node(6)->node(12)->node(17),累计检查3次
如果在此改造的链表基础上,在进行改造,将该链表的每隔4个节点,在新增一个指针,指向下一个同高度的节点
那么检查节点数 不会超过n/4 + 1个
我们要查询值为12的节点,那么我们需要从head节点开始,同时从上至下,依次向后对比。检查路径是head->node(12)->node(17),累计检查2次
如果我们的链表的总长度为n的话,每隔2^n个节点,都新增一个指针的话,那么这个检查路径的复杂度减少为O(logn)
跳过列表算法
在跳跃表这种数据结构中,如何查找、插入和删除元素。下面分别用伪代码的分别演示
查找元素
我们通过遍历指针来查找元素,指针不会超出包含要查找的元素的节点。
假设当前的跳跃表结构如下
Search(list, searchKey)
x := list->header
for i:=list->level downto 1 do
while x->forward[i]->key < searchKey do
x := x->forward[i]
x := x->forward[i]
if x->key = searchKey then return x->value
else return failure
解析:
- 获取当前跳跃表的头节点x := list->header
- 此头节点含有一个level字段,用于记录当前跳跃表中,除了头节点外节点中的高度k的最大值,此图中的k值为3
- 从最高层级别开始向下进行循环。
- 在当前级别中,从左向有右遍历当前层级中的链表,
x->forward[i]是当前节点中的指针,指向下一个节点,如果下一个节点中的key值小于要搜索的searchkey值,则将下一个节点赋值给x,并回到while循环继续查找 - 在for循环中依次向下层遍历,最终获取的x要么是找到对应的searchkey值的接口,要么当前跳跃表中,不存在此值
插入元素
要插入元素,首先也是通过查询方法,找到准备插入新节点的位置。如下图,插入一个值为17的节点
插入节点的伪代码
Insert(list, searchKey, newValue)
local update[maxLevel]
x := list->header
// 查找要插入的节点位置
for i:=list->level downto 1 do
while x->forward[i]->key < searchKey do
x := x->forward
// 保存i级高度,即将插入节点位置的左侧节点
// 从最高层开始,i=4时,update[4] = node(6)
// i = 3时,update[3] = node(6)
// i = 2时,update[2] = node(9)
// i = 1时,update[1] = node(12)
update[i] := x
// 设置x为当前找到的插入位置的前一个接口,即node(12)
x := x->forward
// 如果当前的key值和要插入的searchKey是同一个节点,则直接赋值为新值即可,无需插入节点
if x->key == searchKey then x->value := newValue
else
// 否则
newLevel := randomLevel() // 获取随机高度,假设为2
// 如果生成的随机高度 大于当前的跳跃表高度
if nweLevel > list->level then
// 循环高出的级别部分,并且分别设置指向此节点的前一个节点为头节点
for i:=list->level+1 to newLevel do
update[i] = list->header
// 设置跳跃表的高度为最新的高度值
list->level := newLevel
// 创建新节点
x := makeNode(newLevel, searchKey, value)
// 执行插入节点
// 由下向上进行循环新创建的节点,在搜索节点的过程中,保存了update[]的内容
// update[4] = node(6)
// update[3] = node(6)
// update[2] = node(9)
// update[1] = node(12)
for i:=1 to newLevel do
x->forward[i] := update[i]->forward[i]
update[i]->forward[i] := x
删除元素
删除元素和插入元素类似,也需要先通过查询元素方法,找到想要删除的节点,然后删除节点,依然使用下图示例,删除值为17的这个节点
删除元素的伪代码如下
Delete(list, searchKey)
local update[MaxLevel]
x := list->header
// 搜索节点
for i:=list->level downto 1 do
while x->forward[i]->key < searchKey do
x := x->forward[i]
// 保存i级高度,即将插入节点位置的左侧节点
update[i] := x
x := x->forward[i]
// 如果搜索的节点key值,等于准备删除的searchKey值
if x->key = searchKey then
// 由下到上,开始重新拼接每层链表关系
for i:=1 to list->level do
// 如果i级别高度的forward指针,指向的不是当前x节点,则退出执行
if update[i]->forward[i] != x then break
// 单链表删除节点操作,更改指针
update[i]->forward[i] := x->forward[i]
// 释放节点
free(x)
// 根据删除节点的级别高度,调整跳跃表的高度
while list->level > 1 and list->header->forward[list->level] = NULL do
list->level := list->level - 1