跳表的学习记录
跳表(Skip List)是一种数据结构,它通过在多个层次上添加额外的前向指针来提高有序数据的搜索效率。跳表与其他常见的有序数据结构(如二叉搜索树、平衡树如AVL树和红黑树、B树等)相比,具有其独特的优缺点:
跳表的优点
简单性: 跳表的算法和数据结构相对简单,容易理解和实现。与平衡树的复杂旋转和重新平衡相比,跳表的维护成本较低。
高效的搜索操作: 跳表可以提供接近二叉搜索树的搜索性能,平均时间复杂度为 O(logn)。
灵活的插入和删除: 跳表在插入和删除操作时,只需要调整相邻的指针,而不需要像在平衡树中那样复杂的重新平衡。
有序性: 与哈希表不同,跳表维护了元素的有序性,使得范围查询等操作更加高效。
跳表的缺点
空间消耗: 由于跳表在每个节点中存储多个指针,它通常会消耗比二叉树更多的内存空间。
随机性: 跳表的性能部分依赖于随机化过程,这可能导致性能的不稳定性。虽然在平均情况下表现良好,但最坏情况的表现可能不如某些确定性结构。
缓存不友好: 由于跳表的节点可能分布在内存的不同位置,它可能不如紧凑存储的数据结构(如数组)那样对缓存友好。
举个例子
跳表的 put 操作
想象一下你在一个有多层的购物中心里。每一层都有很多商店,而且每层都有楼梯和电梯连接到其他层。现在,你要在这个购物中心里开一家新店。
put 操作就像是在购物中心里找一个合适的位置来开设你的店铺。
选择楼层: 你首先决定你的店铺要开在哪些楼层。这就像跳表中随机决定节点应该在哪些层上有链接。
找到位置: 接着,你从最高层开始,寻找开设店铺的位置。你会经过每一家店,直到找到合适的空位。这就像在跳表中,从最高层开始向右搜索,直到找到合适的插入点。
开设店铺: 一旦找到位置,你就在那里开设你的店铺,并确保楼梯或电梯可以通到你的店铺。这在跳表中对应于将新节点插入到链表中,并更新相邻节点的指针。
跳表的 del 操作
现在,想象你决定关闭你在购物中心的某个店铺。
del 操作就像是在购物中心中关闭并移除你的店铺。
寻找店铺: 你首先需要找到你的店铺。这就像在跳表中寻找一个特定的节点。
关闭并移除: 一旦找到,你就关闭店铺,并且移除所有通向这家店铺的楼梯和电梯的指示标志。这在跳表中对应于移除一个节点,并更新所有指向该节点的指针,确保它们指向下一个正确的节点。
调整楼层: 如果你关闭的是最高层的唯一店铺,那么整个楼层可能会被关闭。这就像在跳表中,如果删除节点后某一层没有任何节点了,我们就降低跳表的高度。
代码实现
package mainimport ("fmt""math/rand""time"
)type node struct {next []*nodekey, val int
}type SkipList struct {head *node
}func (s *SkipList) search(key int) *node {move := s.headfor level := len(s.head.next) - 1; level >= 0; level-- {for move.next[level] != nil && move.next[level].key < key {move = move.next[level]}if move.next[level] != nil && move.next[level].key == key {return move.next[level]}}return nil
}func (s *SkipList) Get(key int) (int, bool) {if searchNode := s.search(key); searchNode != nil {return searchNode.val, true}return -1, false
}// Adjust the roll method to control the height distribution
func (s *SkipList) roll() int {level := 0for rand.Float32() < 0.5 { // Adjust the probability as neededlevel++}return level
}func (s *SkipList) Put(key, val int) {if search := s.search(key); search != nil {search.val = valreturn}level := s.roll()for len(s.head.next)-1 < level {s.head.next = append(s.head.next, nil)}newNode := node{next: make([]*node, level+1),key: key,val: val,}move := s.headfor l := level; l >= 0; l-- {for move.next[l] != nil && move.next[l].key < key {move = move.next[l]}newNode.next[l] = move.next[l]move.next[l] = &newNode}
}func (s *SkipList) Del(key int) {if _node := s.search(key); _node == nil {return}move := s.headfor level := len(s.head.next) - 1; level >= 0; level-- {for move.next[level] != nil && move.next[level].key < key {move = move.next[level]}if move.next[level] != nil && move.next[level].key == key {move.next[level] = move.next[level].next[level]}}for len(s.head.next) > 1 && s.head.next[len(s.head.next)-1] == nil {s.head.next = s.head.next[:len(s.head.next)-1]}
}func (s *SkipList) ceiling(target int) *node {move := s.headfor level := len(s.head.next) - 1; level >= 0; level-- {for move.next[level] != nil && move.next[level].key < target {move = move.next[level]}if move.next[level] != nil && move.next[level].key == target {return move.next[level]}}return move.next[0]
}func (s *SkipList) Range(start, end int) [][2]int {ceilNode := s.ceiling(start)if ceilNode == nil {return [][2]int{}}var res [][2]intfor move := ceilNode; move != nil && move.key <= end; move = move.next[0] {res = append(res, [2]int{move.key, move.val})}return res
}func main() {sl := createTestSkipList()// Test Insertionfmt.Println("Testing Insertion...")sl.Put(3, 30)sl.Put(1, 10)sl.Put(2, 20)fmt.Println("Insertion Passed")// Test Searchfmt.Println("Testing Search...")if val, found := sl.Get(2); !found || val != 20 {fmt.Printf("Search Failed: expected 20, got %d\n", val)} else {fmt.Println("Search Passed")}// Test Deletionfmt.Println("Testing Deletion...")sl.Del(1)if _, found := sl.Get(1); found {fmt.Println("Deletion Failed: 1 should not exist")} else {fmt.Println("Deletion Passed")}// Test Ceiling Functionfmt.Println("Testing Ceiling Function...")ceilNode := sl.ceiling(2)if ceilNode == nil || ceilNode.key != 2 {fmt.Printf("Ceiling Failed: expected key 2, got %d\n", ceilNode.key)} else {fmt.Println("Ceiling Passed")}// Test Range Queryfmt.Println("Testing Range Query...")expected := [][2]int{{2, 20}, {3, 30}}result := sl.Range(2, 3)if len(result) != len(expected) {fmt.Printf("Range Failed: expected length %d, got %d\n", len(expected), len(result))} else {allMatch := truefor i, pair := range expected {if result[i] != pair {fmt.Printf("Range Failed at index %d: expected %v, got %v\n", i, pair, result[i])allMatch = falsebreak}}if allMatch {fmt.Println("Range Passed")}}
}func createTestSkipList() *SkipList {rand.Seed(time.Now().UnixNano())sl := SkipList{head: &node{next: make([]*node, 1)}}return &sl
}相关文章:
跳表的学习记录
跳表(Skip List)是一种数据结构,它通过在多个层次上添加额外的前向指针来提高有序数据的搜索效率。跳表与其他常见的有序数据结构(如二叉搜索树、平衡树如AVL树和红黑树、B树等)相比,具有其独特的优缺点&am…...
电子学会C/C++编程等级考试2022年09月(二级)真题解析
C/C++等级考试(1~8级)全部真题・点这里 第1题:统计误差范围内的数 统计一个整数序列中与指定数字m误差范围小于等于X的数的个数。 时间限制:5000 内存限制:65536输入 输入包含三行: 第一行为N,表示整数序列的长度(N <= 100); 第二行为N个整数,整数之间以一个空格分…...
如何使用nginx部署静态资源
Nginx可以作为静态web服务器来部署静态资源,这个静态资源是指在服务端真实存在,并且能够直接展示的一些文件数据,比如常见的静态资源有html页面、css文件、js文件、图片、视频、音频等资源相对于Tomcat服务器来说,Nginx处理静态资…...
lua的gc原理
lua垃圾回收(Garbage Collect)是lua中一个比较重要的部分。由于lua源码版本变迁,目前大多数有关这个方面的文章都还是基于lua5.1版本,有一定的滞后性。因此本文通过参考当前的5.3.4版本的Lua源码,希望对Lua的GC算法有一个较为详尽的探讨。 L…...
redis作为缓存详解
目录 前言: 为什么说关系型数据库性能不高 如何提高MySQL并发量 缓存更新策略 定期更新 实时更新 内存淘汰策略 Redis内置的淘汰策略 缓存常见问题 缓存预热 缓存穿透 缓存雪崩 缓存击穿 前言: 对于缓存的理解,缓存目的就是为了…...
231127 刷题日报
这周值班。。多少写道题吧,保持每天的手感。老婆给买了lubuladong纸质书,加油卷。 1. 131. 分割回文串 写个这个吧,钉在耻辱柱上的题。 为啥没写出来: 1. 递归树没画对 把树枝只看做是1个字母,而且不清楚树枝和节点…...
【Linux】vim-多模式的文本编辑器
本篇文章内容和干货较多,希望对大家有所帮助👍 目录 一、vim的介绍 1.1 vi 与 vim的概念1.2 Vim 和 Vi 的一些对比 二、vim 模式之间的切换 2.1 进入vim2.2 [正常模式]切换到[插入模式]2.3 [插入模式]切换至[正常模式]2.4 [正常模式]切换至[底行模式…...
Ubuntu 启用 root 用户
在启用 root 用户之前,我们先来了解一下, ubuntu 命令的组成。 打开 ubuntu 的终端,现在的命令行是由 topeetubuntu:~$ 这几个字母组成,那么这几个字母都代表 什么意思呢? topeet …...
手摸手Element-ui路由VueRoute
后端WebAPI准备 https://router.vuejs.org/zh/guide/ https://v3.router.vuejs.org/zh/installation.html <template><el-table:data"tableData"style"width: 100%":row-class-name"tableRowClassName"><!-- <el-table-colum…...
探究Kafka原理-5.Kafka设计原理和生产者原理解析
👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家📕系列专栏:Spring源码、JUC源码、Kafka原理🔥如果感觉博主的文章还不错的话,请ὄ…...
浅谈C#在unity应用中的工厂模式
文章目录 前言简单工厂模式工厂方法模式抽象工厂模式Unity实战 前言 工厂模式是一种创建型设计模式,它提供了一种将对象的实例化过程封装起来的方法,使得客户端代码不必直接依赖于具体类。这有助于降低代码的耦合度,提高代码的可维护性和可扩…...
卷积神经网络(Inception-ResNet-v2)交通标志识别
文章目录 一、前言二、前期工作1. 设置GPU(如果使用的是CPU可以忽略这步)2. 导入数据3. 查看数据 二、构建一个tf.data.Dataset1.加载数据2. 配置数据集 三、构建Inception-ResNet-v2网络1.自己搭建2.官方模型 五、设置动态学习率六、训练模型七、模型评…...
网易云音频数据如何爬取?
在当今数字化时代,音频数据的获取和处理变得越来越重要。本文将详细介绍如何使用Objective-C语言构建音频爬虫程序,以爬取网易云音乐为案例。我们将从Objective-C的基础知识开始,逐步深入到爬取思路分析、构建爬虫框架、完整爬取代码等方面&a…...
97、Text2NeRF: Text-Driven 3D Scene Generation with Neural Radiance Fields
简介 论文地址 使用扩散模型来推断文本相关图像作为内容先验,并使用单目深度估计方法来提供几何先验,并引入了一种渐进的场景绘制和更新策略,保证不同视图之间纹理和几何的一致性 实现流程 简单而言: 文本-图片扩散模型生成一…...
【C++】多态(上) 多态 | 虚函数 | 重写 | final、override | 接口继承与实现继承 | 抽象类
一、多态 概念 多态,就是多种状态,即不同的对象去完成同一个行为时会产生出不同的状态。比如:买票时,成人要原价买,学生和老人就可以享受优惠价便宜一点儿。同样是买票这个行为,不同的对象来做就有不同的…...
国内怎么投资黄金,炒黄金有哪些好方法?
随着我国综合实力的不断强大,投资市场的发展也日臻完善,现已成为了国际黄金市场的重要组成部分,人们想要精准判断金市走向,就离不开对我国经济等信息的仔细分析。而想要有效提升盈利概率,人们还需要掌握国内黄金投资的…...
springboot实现数据脱敏
springboot实现数据脱敏 怎么说呢,写着写着发觉 ”这写的什么玩意“ 。 总的来说就是,这篇文章并不能解决数据脱敏问题,但以下链接可以。 SpringBoot中利用自定义注解优雅地实现隐私数据脱敏 然后回到本文,本来是想基于AOP代理&am…...
uniapp实现多时间段设置
功能说明: 1 点击新增时间,出现一个默认时间段模板,不能提交 2 点击“新增时间文本”,弹出弹窗,选择时间,不允许开始时间和结束时间同时为00:00, <view class"item_cont"> …...
uni-app - 去除隐藏页面右侧垂直滚动条
全局配置 "globalStyle": { //全局配置 "scrollIndicator":"none", // 不显示滚动条 "app-plus":{ "scrollIndicator":"none" // 在APP平台都不显示滚动条 } }局部配置 "path": "pages/ind…...
一次简单的 Http 请求异常处理 (请求的 url 太长, Nginx 直接返回 400, 导致请求服务异常)
1 结论 按照惯例直接说结论。 后台服务 A 有一个 Http 接口, 代码如下: RequestMapping(value "/user", method RequestMethod.GET) public List<UserInfoVo> getUserInfoByUserIds(RequestParam(value "userIds") List<String> userIds…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
