skip list

无标题
#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <ctime>#define SKIP_LIST_MAX_LEVEL 4// 跳表连接item
typedef struct skip_list_item
{struct skip_list_node *prev; // 上一个节点指针,指向头,&head[0]/headstruct skip_list_node *next; // 下一个节点指针
} skip_list_item_t;// 跳表节点
typedef struct skip_list_node
{skip_list_item_t item[SKIP_LIST_MAX_LEVEL]; // 跳表节点itemuint32_t key;                                // 用于排序的keyuint32_t level;                              // 节点当前所在层级
} skip_list_node_t;// 跳表结构
typedef struct skip_list
{skip_list_node_t head;  // 跳表每层的head,head.item[0]为原表uint64_t size;          // 跳表节点总数uint32_t level;         // 下次节点插入跳表层数uint32_t level_max;     // 跳表最大层数
} skip_list_t;/*******************************************************************************/
skip_list_t *skip_list_create()
{skip_list_t *skip_list = (skip_list_t *)calloc(1, sizeof(skip_list_t));skip_list->size = 0;skip_list->level = 0;skip_list->level_max = SKIP_LIST_MAX_LEVEL;return skip_list;
}void skip_list_destroy(skip_list_t *skip_list)
{skip_list_node_t *node = skip_list->head.item[0].next;while (node != NULL){skip_list_node_t *next = node->item[0].next;free(node);node = next;}free(skip_list);
}void skip_list_node_init(skip_list_node_t *node, uint32_t key, uint32_t level)
{memset(&node->item, 0, sizeof(node->item));node->key = key;node->level = level;
}void skip_list_update_level(skip_list_t *skip_list)
{uint32_t level = 1;while ((rand() & 65535) < 32768){level++;}level = level < SKIP_LIST_MAX_LEVEL ? level : SKIP_LIST_MAX_LEVEL;skip_list->level = level - 1;
}void skip_list_insert(skip_list_t *skip_list, skip_list_node_t *node)
{skip_list_update_level(skip_list);node->level = skip_list->level;skip_list_node_t *update[SKIP_LIST_MAX_LEVEL];skip_list_node_t *x = &skip_list->head;int i = SKIP_LIST_MAX_LEVEL - 1;// 找到每一层要插入的位置while (i >= 0){while (x->item[i].next != NULL && x->item[i].next->key < node->key){x = x->item[i].next;}update[i] = x;i--;}// 执行插入操作for (int i = 0; i <= (int)node->level; i++){node->item[i].next = update[i]->item[i].next;if (update[i]->item[i].next != NULL){update[i]->item[i].next->item[i].prev = node;}update[i]->item[i].next = node;node->item[i].prev = update[i];}skip_list->size++;
}skip_list_node_t *skip_list_find(skip_list_t *skip_list, uint32_t key)
{if (skip_list->size == 0)return NULL; // 空表skip_list_node_t *x = &skip_list->head;int i = SKIP_LIST_MAX_LEVEL - 1;// 先找到节点while (i >= 0){while (x->item[i].next != NULL && x->item[i].next->key < key){x = x->item[i].next;}i--;}x = x->item[0].next;if (x != NULL && x->key == key){return x;}return NULL;
}skip_list_node_t *skip_list_find_max(skip_list_t *skip_list)
{if (skip_list->size == 0)return NULL; // 空表skip_list_node_t *x = &skip_list->head;int i = SKIP_LIST_MAX_LEVEL - 1;// 找到最大节点while (i >= 0){while (x->item[i].next != NULL){x = x->item[i].next;}i--;}if (x == &skip_list->head)return NULL;return x;
}skip_list_node_t *skip_list_find_min(skip_list_t *skip_list)
{if (skip_list->size == 0)return NULL; // 空表skip_list_node_t *x = skip_list->head.item[0].next;if (x == NULL)return NULL;return x;
}void skip_list_print(skip_list_t *skip_list)
{for (int i = SKIP_LIST_MAX_LEVEL - 1; i >= 0; i--){skip_list_node_t *x = skip_list->head.item[i].next;std::cout << "Level " << i << ": ";while (x != NULL){std::cout << x->key << " ";x = x->item[i].next;}std::cout << std::endl;}
}int main()
{skip_list_t *skip_list = skip_list_create();srand(time(NULL));uint32_t keys[] = {3, 6, 7, 9, 12, 19, 17, 26, 21, 25};for (uint32_t key : keys){skip_list_node_t *node = (skip_list_node_t *)malloc(sizeof(skip_list_node_t));skip_list_node_init(node, key, 0);skip_list_insert(skip_list, node);}std::cout << "Skip List:" << std::endl;skip_list_print(skip_list);uint32_t search_key = 19;skip_list_node_t *found = skip_list_find(skip_list, search_key);std::cout << "Search for " << search_key << ": " << (found ? "Found" : "Not Found") << std::endl;search_key = 15;found = skip_list_find(skip_list, search_key);std::cout << "Search for " << search_key << ": " << (found ? "Found" : "Not Found") << std::endl;skip_list_destroy(skip_list);return 0;
} 

skip_list_insert 函数流程

随机决定新节点的层数

void skip_list_insert(skip_list_t *skip_list, skip_list_node_t *node)
{skip_list_update_level(skip_list);  // 随机更新跳表的层数node->level = skip_list->level;     // 设置新节点的层数
}

skip_list_update_level(skip_list) 会随机决定新节点需要占据的层数(根据概率,高层节点少,低层节点多,类似于抛硬币)。
新节点 node 的层数被设置为刚刚生成的随机层数 skip_list->level。

找到每一层要插入的位置

skip_list_node_t *update[SKIP_LIST_MAX_LEVEL];  // 保存插入位置的前驱节点
skip_list_node_t *x = &skip_list->head;         // 从跳表的头节点开始
int i = SKIP_LIST_MAX_LEVEL - 1;// 找到每一层要插入的位置
while (i >= 0)
{while (x->item[i].next != NULL && x->item[i].next->key < node->key){x = x->item[i].next;  // 在第 i 层向右移动,寻找插入位置}update[i] = x;  // 记录需要在第 i 层插入的位置的前一个节点i--;  // 进入下一层
}skip_list_node_t *update[SKIP_LIST_MAX_LEVEL];  // 保存插入位置的前驱节点
skip_list_node_t *x = &skip_list->head;         // 从跳表的头节点开始
int i = SKIP_LIST_MAX_LEVEL - 1;// 找到每一层要插入的位置
while (i >= 0)
{while (x->item[i].next != NULL && x->item[i].next->key < node->key){x = x->item[i].next;  // 在第 i 层向右移动,寻找插入位置}update[i] = x;  // 记录需要在第 i 层插入的位置的前一个节点i--;  // 进入下一层
}


初始化了一个 update 数组,用于保存插入位置前驱节点。
设置一个指针 x 指向跳表的头节点,从最高层 (i = SKIP_LIST_MAX_LEVEL - 1) 开始。
通过内层循环,在每一层查找当前节点 x 所需插入位置的前一个节点,并将该节点记录到 update 数组。
如果 x->item[i].next 不为空且 x->item[i].next->key 小于 node->key,则 x 继续向右移动。
一旦找到适合插入位置的节点,记录在 update[i] 中。
逐层向下(i–)重复上述步骤,直到最低层(层0)。
3. 执行插入操作
// 执行插入操作
for (int i = 0; i <= (int)node->level; i++)
{
node->item[i].next = update[i]->item[i].next; // 新节点的 next 指向前驱节点的 next
if (update[i]->item[i].next != NULL)
{
update[i]->item[i].next->item[i].prev = node; // 前驱节点的 next 指向新节点
}
update[i]->item[i].next = node; // 前驱节点的 next 指向新节点
node->item[i].prev = update[i]; // 新节点的 prev 指向前驱节点
}

skip_list->size++;  // 更新跳表中的节点数

}
复制
// 执行插入操作
for (int i = 0; i <= (int)node->level; i++)
{
node->item[i].next = update[i]->item[i].next; // 新节点的 next 指向前驱节点的 next
if (update[i]->item[i].next != NULL)
{
update[i]->item[i].next->item[i].prev = node; // 前驱节点的 next 指向新节点
}
update[i]->item[i].next = node; // 前驱节点的 next 指向新节点
node->item[i].prev = update[i]; // 新节点的 prev 指向前驱节点
}

skip_list->size++;  // 更新跳表中的节点数

}

遍历从第0层到新节点的最大层 node->level:
node->item[i].next = update[i]->item[i].next;:新节点 node 的下一个节点指向更新节点 update[i] 的下一个节点。
if (update[i]->item[i].next != NULL):如果 update[i]->item[i].next 不是空,则更新下一个节点的 prev 指针指向新节点 node。
update[i]->item[i].next = node;:更新节点 update[i] 的下一个节点指向新节点 node。
node->item[i].prev = update[i];:新节点 node 的前一个节点指向更新节点 update[i]。
最后,更新跳表的节点总数 skip_list->size++。
插入过程示意图
假设跳表如下,准备插入节点 node(key为10):

当前跳表:
Level 3: head -> 15 -> NULL
Level 2: head -> 9 -> 15 -> NULL
Level 1: head -> 7 -> 9 -> 15 -> NULL
Level 0: head -> 3 -> 7 -> 9 -> 15 -> NULL
复制
当前跳表:
Level 3: head -> 15 -> NULL
Level 2: head -> 9 -> 15 -> NULL
Level 1: head -> 7 -> 9 -> 15 -> NULL
Level 0: head -> 3 -> 7 -> 9 -> 15 -> NULL

  1. 查找插入位置:
    在每一层中找到应插入位置前的节点。

update[] = { head, 7, 9, 9 };
复制
update[] = { head, 7, 9, 9 };

2. 执行插入:
假设新节点随机生成的层数为2(第三层也有节点)。

把新节点插入适当位置后:

插入 key=10 后的跳表:
Level 3: head -> 10 -> 15 -> NULL
Level 2: head -> 9 -> 10 -> 15 -> NULL
Level 1: head -> 7 -> 9 -> 10 -> 15 -> NULL
Level 0: head -> 3 -> 7 -> 9 -> 10 -> 15 -> NULL
复制
插入 key=10 后的跳表:
Level 3: head -> 10 -> 15 -> NULL
Level 2: head -> 9 -> 10 -> 15 -> NULL
Level 1: head -> 7 -> 9 -> 10 -> 15 -> NULL
Level 0: head -> 3 -> 7 -> 9 -> 10 -> 15 -> NULL

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/146132.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于单片机汽车驾驶防瞌睡防疲劳报警器自动熄火设计

文章目录 前言资料获取设计介绍功能介绍设计程序具体实现截图设计获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对…

项目第四弹:交换机、队列、绑定信息管理模块分析与代码实现

项目第四弹&#xff1a;交换机、队列、绑定信息管理模块分析与代码实现 一、模块设计分析1.模块划分2.功能需求 二、交换机模块的实现1.交换机结构体的实现2.交换机持久化管理模块的实现3.交换机对外管理模块实现声明、删除交换机时的查找不能复用exists函数为何持久化管理模块…

查找算法 01分块查找

自己设计一个分块查找的例子&#xff0c;不少于15个数据元素&#xff0c;并建立分块查找的索引 基于上述例子&#xff0c;计算查找成功的ASL、查找失败的ASL 拓展&#xff1a; ‌‌分块查找的平均查找长度&#xff08;‌ASL&#xff09;的计算公式如下‌&#xff1a;‌ ‌顺序…

ESP32 JTAG 调试

前言 个人邮箱&#xff1a;zhangyixu02gmail.com本人使用的是 Ubuntu 环境&#xff0c;采用 GDB 方式进行调试。对于新手&#xff0c;我个人还是建议参考ESP32S3学习笔记&#xff08;0&#xff09;—— Vscode IDF环境搭建及OpenOCD调试介绍进行图形化的方式调试。如果是希望在…

占领矩阵-第15届蓝桥省赛Scratch中级组真题第5题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第190讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

Python酷库之旅-第三方库Pandas(122)

目录 一、用法精讲 541、pandas.DataFrame.take方法 541-1、语法 541-2、参数 541-3、功能 541-4、返回值 541-5、说明 541-6、用法 541-6-1、数据准备 541-6-2、代码示例 541-6-3、结果输出 542、pandas.DataFrame.truncate方法 542-1、语法 542-2、参数 542-3…

植保无人机是朝阳产业还是夕阳产业?

植保无人机产业是朝阳产业还是夕阳产业&#xff0c;可以从多个维度进行分析&#xff1a; 一、市场需求与增长趋势 市场需求&#xff1a;随着农业现代化的推进和劳动力成本的上升&#xff0c;植保无人机因其高效、安全、节省农药等优势&#xff0c;在农业生产中的应用越来越广…

自闭症能上寄宿学校吗?了解解答与选择

在探讨自闭症儿童教育的话题时&#xff0c;寄宿学校作为一种特殊的教育模式&#xff0c;常常引发家长们的关注与讨论。对于自闭症儿童而言&#xff0c;寄宿学校既是一个充满挑战的新环境&#xff0c;也是一个能够促进他们独立成长与社交融合的重要平台。今天&#xff0c;我们将…

自制数据库空洞率清理工具-C版-03-EasyClean-V1.3(支持南大通用数据库Gbase8a)

目录 一、环境信息 二、简述 三、升级点 四、支持功能 五、空洞率 六、工具流程图 1、流程描述 2、注意点 &#xff08;1&#xff09;方法一 &#xff08;2&#xff09;方法二 七、清理空洞率流程图 八、安装包下载地址 九、参数介绍 1、命令模板 2、命令样例 3…

【C语言-数据结构】单链表的定义

单链表的定义&#xff08;实现&#xff09; 比较顺序表和单链表的物理存储结构就能够清楚地发现二者的区别 用代码定义一个单链表 typedef struct LNode{ElemType data; //每个结点存放一个数据元素struct LNode* next; //指针指向下一个结点 }LNode, *LinkList;//要表示一个…

[JavaEE] TCP协议

目录 一、TCP协议段格式 二、TCP确保传输可靠的机制 2.1 确认应答 2.2 超时重传 2.3 连接管理 2.3.1 三次握手 2.3.2 四次挥手 2.4 滑动窗口 2.4.1 基础知识 2.4.2 两种丢包情况 2.4.2.1 数据报已经抵达&#xff0c;ACK丢包 2.4.2.2 数据包丢包 2.5 流量控制…

【时时三省】(C语言基础)指针笔试题2

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 笔试题2 这里的0x1是16进制的1 跟十进制的1一样 这道题考察的是&#xff1a;指针类型决定了指针的运算 p是上面结构体的指针 它指向的大小结果是20个字节 指针…

项目第五弹:队列消息管理模块

项目第五弹&#xff1a;队列消息管理模块 一、消息如何组织并管理1.消息结构体2.消息持久化管理模块设计1.数据消息文件名2.临时消息文件名3.对外接口与包含成员 二、自定义应用层协议解决文件读写的粘包问题1.Length-Value协议 三、队列消息管理模块设计1.待确认消息哈希表2.待…

[数据结构]动态顺序表的实现与应用

文章目录 一、引言二、动态顺序表的基本概念三、动态顺序表的实现1、结构体定义2、初始化3、销毁4、扩容5、缩容5、打印6、增删查改 四、分析动态顺序表1、存储方式2、优点3、缺点 五、总结1、练习题2、源代码 一、引言 想象一下&#xff0c;你有一个箱子&#xff08;静态顺序…

【医学半监督】对比互补掩蔽的自监督预训练半监督心脏图像分割

SELF-SUPERVISED PRE-TRAINING BASED ON CONTRASTIVE COMPLEMENTARY MASKING FOR SEMI-SUPERVISED CARDIAC IMAGE SEGMENTATION 2024 IEEE International Symposium on Biomedical Imaging (ISBI) 摘要: 心脏结构分割对心脏病诊断非常重要,而使用大量注释的深度学习在这项任…

Buck变换器闭环控制,simulink仿真模型(适合初学者学习)

Buck变换器&#xff0c;又称为降压斩波器&#xff0c;是一种常见的DC-DC转换器&#xff0c;广泛应用于电源管理领域。它通过开关元件&#xff08;通常是MOSFET或BJT&#xff09;的导通与截止&#xff0c;改变输入电压到负载的平均电压&#xff0c;从而实现电压的降低。在实际应…

harbor私有镜像仓库,搭建及管理

私有镜像仓库 docker-distribution docker的镜像仓库&#xff0c;默认端口号5000 做个仓库&#xff0c;把镜像放里头&#xff0c;用什么服务&#xff0c;起什么容器 vmware公司在docker私有仓库的基础上做了一个web页面&#xff0c;是harbor docker可以把仓库的镜像下载到本地&…

tauri嵌入自定义目录/文件,并在代码中读取文件内容的操作流程

可以看官方文档&#xff1a;Embedding Additional Files | Tauri Apps 在绑定了文件之后&#xff0c;可以在js中访问嵌入的文件或者在rust中读取嵌入的文件内容&#xff0c;详细的配置操作如下。 在src-tauri中创建自定义文件夹或文件&#xff0c;并在在tauri.conf.json中配置…

Java多线程Thread及其原理深度解析

文章目录 1. 实现多线程的方式2. Thread 部分源码2.1. native 方法注册2.2. Thread 中的成员变量2.3. Thread 构造方法与初始化2.4. Thread 线程状态与操作系统状态2.4. start() 与 run() 方法2.5. sleep() 方法2.6. join() 方法2.7. interrupt() 方法 本文参考&#xff1a; 线…

Spring自定义参数解析器

在这篇文章中&#xff0c;我们认识了参数解析器和消息转换器&#xff0c;今天我们来自定义一个参数解析器。 自定义参数解析器 实现HandlerMethodArgumentResolver的类&#xff0c;并注册到Spring容器。 Component&#xff0f;&#xff0f;注册到Spring public class UserAr…