Skip List

什么是skip list?

“Skip lists are a data structure that can be used in place ofbalanced trees. Skip lists use probabilistic balancing rather than strictlyenforced balancing and as a result the algorithms for insertion and deletion inskip lists are much simpler and significantly faster than equivalent algorithmsfor balanced trees.“

 

如上为skip list作者William Pugh对skip list的解释,跳表是一种可以替代平衡树的数据结构,跳表使用的平衡算法并不是严格要求的平衡,这使插入和删除操作是更简单,并且明显比平衡树快速。

 

跳表数据结构


如上图中的a链表,是一个有序链表,如果从中找一个节点,最多可能需要遍历每一个节点,那么时间复杂度就是O(n);n为链表中节点的个数。




如上图中的b链表,每隔一个节点多了一层指针,指向它前面第二2个节点,那么我们要查找一个节点,最多可能需要遍历(n/2)+ 1个节点,时间复杂度就是O(n/2);

 


如上图中的c链表,在b链表的基础上多了一层指针,每第四个节点,指向它前面第四个节点,那么我们要查找一个节点,最多可能需要遍历(n/4)+ 2个节点,时间复杂度就是O(n/4);

 


如上图中的d链表,如果每第2^i个节点有指针指向它前面第2^i个节点,那么查找某个节点的时间复杂度可以降低到O(logn),这种结构查询起来很快速,但是插入和删除确是不切实际的。

 


一个节点有k个向前的指针,那么就称这个节点为k层节点,每个节点的层数是随机产生的,产生level 1的概率是50%,level2的概率是25%,level 3的概率是12.5%……,每个节点的层数的产生要保证采用这种相同的机制;每个节点的第i层指针,指向下一个至少有i层的节点。因为会滑过一些中间节点,所以称为跳表。

 

跳表算法

查找(伪码如下)

Search(list, searchKey)

x :=list→header

--loop invariant: x → key < searchKey

for  i := list→level  downto 1 do

while x →forward[i]→ key < searchKey do

x :=  x →forward[i]

--  x→ key < searchKey ≤ x→ forward[1] → key

x:=  x →forward[1]

if  x →key = searchKey  then return x → value

else  return  failure

  

    从顶层开始找,每层中寻找比目标小的最大的一个节点,然后转到下一层,然后向前移动一个节点,如果相等就代表找到了,如果不相等,就代表没有这个节点。

插入(伪码如下)


Insert(list, searchKey, newValue)

local  update[1..MaxLevel]

x :=list→header

for  i := list→level  downto 1 do

while x →forward[i]→ key < searchKey do

x :=  x →forward[i]

-- x →key < searchKey  ≤ x →forward[i] →key

update[i] :=  x

x:=  x →forward[1]

if  x →key = searchKey  then x → value :=  newValue

else

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 := 1  to level  do

x →forward[i] := update[i] →forward[i]

update[i] →forward[i] := x

从顶层开始找,每层中寻找比目标小的最大的一个节点,并记录该节点到update数组,如果新节点的层数大于之前的,则更新list→level,并将list→header放到update数组,其实update数组存放的就是i层中指向目标节点(正插入)的前一个节点,所以最后就是循环每一层,将每层中指向目标的前一节点的对应指针指向目标节点,目标节点的下一个节点指向前一个节点的下一个节点。

 

删除(伪码如下)

Delete(list, searchKey)

local  update[1..MaxLevel]

x := list→header

for  i := list→ level  downto 1 do

while x→forward[i]→ key < searchKey  do

x :=  x →forward[i]

update[i]:=  x

x :=  x →forward[1]

if  x →key = searchKey  then

for  i := 1 to  list → level  do

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] = NIL  do

list→level := list →level – 1

从顶层开始找,每层中寻找比目标小的最大的一个节点,并记录该节点到update数组,循环每层,将update中指向目标节点(被删除的节点)的指针指向目标节点的下一个节点;如果被删节点的层数是最高的,就要更新list→level,减去高出的部分。

### 跳表(Skiplist数据结构概述 跳表是一种基于概率的有序链表扩展的数据结构,它通过多层索引来加速查询操作。每一层都是一个有序链表,上一层是下一层的一个子集。这种设计使得跳表能够在平均 \(O(\log n)\) 的时间复杂度内完成插入、删除和查找操作。 #### 跳表的核心特性 - **层次化结构**:每层是一个独立的有序链表,最底层包含所有节点,而高层逐渐减少节点数量。 - **随机性**:新节点被提升到更高层数的概率通常设置为固定值(如 0.5),这决定了跳表的高度以及性能表现[^3]。 以下是跳表的一种典型实现方式: ```c #include <stdio.h> #include <stdlib.h> #include <time.h> #define MAX_LEVEL 16 typedef struct Node { int value; struct Node **forward; // 指向下一节点的指针数组 } Node; typedef struct SkipList { int level; // 当前最大层数 int size; // 节点总数 Node *header; // 头结点 float p; // 提升概率 } SkipList; // 创建一个新的节点 Node* create_node(int value, int level) { Node *node = (Node *)malloc(sizeof(Node)); node->value = value; node->forward = (Node **)malloc((level + 1) * sizeof(Node *)); for (int i = 0; i <= level; ++i) { node->forward[i] = NULL; } return node; } // 初始化跳表 SkipList* init_skiplist(float probability) { SkipList *list = (SkipList *)malloc(sizeof(SkipList)); list->level = 0; list->size = 0; list->p = probability; list->header = create_node(-1, MAX_LEVEL); return list; } // 随机生成节点的最大层数 int random_level(float p) { int lvl = 0; while ((rand() / (float)RAND_MAX) < p && lvl < MAX_LEVEL) { lvl++; } return lvl; } // 查找指定值的位置 Node* search_value(SkipList *list, int value) { Node *current = list->header; for (int i = list->level; i >= 0; --i) { while (current->forward[i] != NULL && current->forward[i]->value < value) { current = current->forward[i]; } } current = current->forward[0]; // 移动到最后可能匹配的节点 if (current != NULL && current->value == value) { return current; } else { return NULL; } } ``` 上述代码展示了如何初始化跳表并执行基本的查找操作。其中 `random_level` 函数用于决定新节点可以达到的最大层数,从而控制跳表的整体高度[^4]。 --- 关于提到的错误码 **15445** 并未在已知的标准数据库或编程环境中找到具体定义。如果该错误码特定环境相关,则需进一步确认其上下文含义。然而,在 SQL 或其他存储系统中,类似的错误码通常表示权限不足或者资源不可访问等问题[^1]。 对于跳表而言,常见的错误场景包括但不限于: - 插入失败:由于内存分配问题或其他异常情况导致无法创建新的节点。 - 删除失败:目标节点不存在于跳表中。 - 查询超时:当跳表过大且缺乏优化时可能出现性能瓶颈。 这些潜在问题可以通过严格的边界条件检测以及合理的参数配置来规避。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值