详解Golang如何实现支持随机删除元素的堆
知识点掌握了,还需要不断练习才能熟练运用。下面golang学习网给大家带来一个Golang开发实战,手把手教大家学习《详解Golang如何实现支持随机删除元素的堆》,在实现功能的过程中也带大家重新温习相关知识点,温故而知新,回头看看说不定又有不一样的感悟!
背景
堆是一种非常常用的数据结构,它能够支持在O(1)的时间复杂度获取到最大值(或最小值),因此我们经常在需要求最值的场景使用它。
然而普通堆它有一个缺点,它没办法快速的定位一个元素,因此它也没办法快速删除一个堆中元素,需要遍历整个堆去查询目标元素,时间复杂度是O(n),因为堆的结构在逻辑上是这样的:
在实际实现中一般是使用一个数组来模拟:
也就是除了最大值(大顶堆)或最小值(小顶堆),其他元素其实是没有排序的,因此没办法通过二分查找的方式快速定位。
但是我们经常有一种场景,需要堆的快速求最值的性质,又需要能够支持快速的随机访问元素,特别是删除元素。
比如延迟任务的场景,我们可以使用堆对任务的到期时间戳进行排序,从而实现到期任务自动执行,但是它没办法支持随机删除一个延迟任务的需求。
下面将介绍一种支持O(log(n))随机删除元素的堆。
原理
数据结构
一种能够快速随机访问元素的数据结构是map,map如果是哈希表实现则能够在O(1)的复杂度下随机访问任何元素,而heap在知道要删除的元素的下标的情况下,也可以在O(log(n))的复杂度删除一个元素。因此我们可以结合这两个数据结构,使用map来记录heap中每个元素的下标,使用heap来获取最值。
比如对于上面的堆,我们首先给每个元素添加一个Key,这样我们才能够使用map:
然后我们使用map记录每个key对应的下标:
随机访问
这时候比如我们最简单的随机访问一个元素C,那么就是从map[C]
得到元素在heap里面的index=2,因此可以拿到元素的值。
index = m[C] // 获取元素C在heap的下标 return h[index] // 获取index在heap的值
删除
而如果我们要删除元素C,我们也是从map[C]
得到元素在heap里面的index=2,然后把index为2的元素和堆最后一个元素交换,然后从index=2向上和向下调整堆,也就是:
index = m[C] // 获取元素C在heap的下标 swap(index, n - 1) // 和最后一个元素交换 pop() // 移除最后一个元素,也就是元素C down(index) // 从index向下调整堆 up(index) // 从index向上调整堆
map里面的元素index维护
上面使用的swap(i, j)和pop()操作都会影响到map里面元素的index,其实还有push(k, v)操作也会影响元素的index。
对于swap(i, j)来说需要交换两个元素的index:
h[i], h[j] = h[j], h[i] // 交换堆中两个元素 m[h[i].Key] = i // 交换map元素索引 m[h[j].Key] = j // 交换map元素索引
pop()需要删除元素的索引:
elem = h.removeLast() // 移除最后一个元素 m.remove(elem.Key) // 移除元素索引
push(k, v)需要添加元素索引:
m[key] = n // 添加元素索引 h.addLast(Entry(K, V)) // 添加元素到堆
这样其他操作就不需要维护map里面的索引问题了,保持和正常的堆的实现逻辑基本一致。
Golang实现
由于这个数据结构使用到了map和heap,因此起了一个比较短的名字叫meap
,也就是m[ap]+[h]eap
。
数据结构
其中主要就是一个heap和一个map,由于我们也需要从heap到map的操作,因此heap里面每个元素Entry都记录了Key,这样就可以从heap快速访问到map里面的元素,实现map里面index的修改。
LessFunc主要用于比较两个元素大小。
type Meap[K comparable, V any] struct { h []Entry[K, V] m map[K]int lessFunc LessFunc[K, V] } type Entry[K comparable, V any] struct { Key K Value V } type LessFunc[K comparable, V any] func(e1 Entry[K, V], e2 Entry[K, V]) bool
移除堆顶元素
这里和正常的堆的逻辑是一样的,也就是把堆顶元素和最后一个元素交换,然后调整堆,移除堆中最后一个元素。
func (h *Meap[K, V]) Pop() Entry[K, V] { n := h.Len() - 1 h.swap(0, n) // 堆顶和最后一个元素交换 h.down(0, n) // 调整堆 return h.pop() // 移除堆中最后一个元素 }
添加元素
这里的参数和普通的堆有一点区别,也就是我们有Key和Value,因为map必须使用一个Key,因此多了一个Key参数。
由于有map的存在,我们可以先判断元素是否已经存在,如果存在则设置Value然后调整堆即可。
如果不存在则添加元素到堆的最后,然后调整堆。
func (h *Meap[K, V]) Push(key K, value V) { // 如果堆中已经包含这个元素 // 更新值并调整堆 if h.Contains(key) { index := h.m[key] // 元素在堆中下标 h.h[index].Value = value // 更新堆中元素值 h.fix(index) // 向上向下调整堆 return } // 否则添加元素 h.push(key, value) // 添加元素到堆的最后面 h.up(h.Len() - 1) // 向上调整堆 }
移除元素
我们首先通过key定位元素在堆中下标,然后把这个下标和堆最后一个元素交换,调整堆,最后把堆最后一个元素移除。
func (h *Meap[K, V]) Remove(key K) { i, ok := h.m[key] // 获取元素在堆中下标 if !ok { // 如果元素不存在则直接返回 return } n := h.Len() - 1 // 最后一个元素索引 if n != i { // 如果被移除的元素就是堆中最后一个元素,则没必要调整了,直接移除即可 h.swap(i, n) // 和最后一个元素交换 if !h.down(i, n) { // 向下调整 h.up(i) // 向上调整 } } h.pop() // 移除堆中最后一个元素 }
push()、pop()和swap()
涉及到调整map中index的操作都集中到这三个操作之中:
// swap两个元素的时候 // 两个元素在map里的下标也要交换 func (h *Meap[K, V]) swap(i, j int) { h.h[i], h.h[j] = h.h[j], h.h[i] // 交换两个元素 h.m[h.h[i].Key] = i // 更新索引 h.m[h.h[j].Key] = j // 更新索引 } // 添加一个元素到堆的末尾 func (h *Meap[K, V]) push(key K, value V) { h.m[key] = h.Len() // 添加索引 h.h = append(h.h, Entry[K, V]{ // 添加元素到堆尾 Key: key, Value: value, }) } // 从堆的末尾移除元素 func (h *Meap[K, V]) pop() Entry[K, V] { elem := h.h[h.Len()-1] // 堆尾元素 h.h = h.h[:h.Len()-1] // 移除堆尾元素 delete(h.m, elem.Key) // 删除堆尾元素索引 return elem }
时间复杂度
上面Golang实现中关键操作的时间复杂度:
操作 | 时间复杂度 | 描述 |
---|---|---|
Push() | O(log(n)) | 添加元素 |
Pop() | O(log(n)) | 移除堆顶元素 |
Peek() | O(1) | 获取堆顶元素 |
Get() | O(1) | 获取元素 |
Remove() | O(log(n)) | 删除元素 |
总结
map的引入解决了heap无法随机删除的问题,从而能够解决更多的最值问题。其实还有其他的数据结构也能够高效的解决最值问题,比如红黑树能够在O(log(n))获取最大最小值,zset
也可以在O(log(n))的复杂度下获取最值,而且它们也是能够支持随机删除。当然如果你所使用的语言不支持红黑树或者zset,那么使用map+heap实现也是可以的,毕竟实现一个可删除的堆比实现一个红黑树或者zset容易很多,而且meap获取最值的Peek()操作的时间复杂度是O(1)。
以上就是《详解Golang如何实现支持随机删除元素的堆》的详细内容,更多关于golang的资料请关注golang学习网公众号!

- 上一篇
- 一文详解Go语言单元测试的原理与使用

- 下一篇
- 深度解密Go语言中字符串的使用
-
- 善良的台灯
- 这篇文章出现的刚刚好,好细啊,真优秀,mark,关注师傅了!希望师傅能多写Golang相关的文章。
- 2023-03-23 20:12:46
-
- 心灵美的白云
- 太给力了,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢楼主分享技术贴!
- 2023-01-26 16:03:21
-
- 凶狠的小虾米
- 这篇博文真是及时雨啊,太全面了,很有用,收藏了,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-01-13 04:32:40
-
- 会撒娇的香菇
- 这篇博文真及时,细节满满,很棒,收藏了,关注大佬了!希望大佬能多写Golang相关的文章。
- 2023-01-12 12:57:33
-
- 有魅力的鼠标
- 真优秀,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢作者大大分享技术文章!
- 2023-01-09 03:52:12
-
- 壮观的大侠
- 细节满满,码起来,感谢作者的这篇技术文章,我会继续支持!
- 2023-01-07 03:18:12
-
- 慈祥的老师
- 这篇技术贴太及时了,楼主加油!
- 2023-01-05 08:06:23
-
- 重要的菠萝
- 感谢大佬分享,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢楼主分享文章!
- 2023-01-01 16:39:46
-
- 大力的柚子
- 这篇技术文章出现的刚刚好,细节满满,写的不错,码住,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-01-01 02:24:13
-
- 内向的铃铛
- 这篇文章太及时了,好细啊,太给力了,码起来,关注老哥了!希望老哥能多写Golang相关的文章。
- 2022-12-31 20:50:23
-
- 痴情的鞋垫
- 很详细,已加入收藏夹了,感谢作者大大的这篇技术贴,我会继续支持!
- 2022-12-31 00:51:59
-
- Golang · Go教程 | 6小时前 |
- Golang反射详解:基础到高级用法全解析
- 150浏览 收藏
-
- Golang · Go教程 | 7小时前 |
- Golang读写Excel教程:Excelize使用指南
- 401浏览 收藏
-
- Golang · Go教程 | 7小时前 |
- Go语言new与make区别详解
- 143浏览 收藏
-
- Golang · Go教程 | 7小时前 |
- Golang跨进程通信优化:共享内存与Unix套接字对比
- 494浏览 收藏
-
- Golang · Go教程 | 7小时前 |
- Golang模块打包与仓库上传指南
- 433浏览 收藏
-
- Golang · Go教程 | 7小时前 | 相对路径 GoModules go.mod 模块路径 internal目录
- Golang模块引用方法与路径设置技巧
- 163浏览 收藏
-
- Golang · Go教程 | 7小时前 |
- Golang集成BoltDB实现本地KV存储方案
- 417浏览 收藏
-
- Golang · Go教程 | 7小时前 |
- Go语言匿名函数详解:实现Lambda表达式
- 212浏览 收藏
-
- Golang · Go教程 | 8小时前 | golang 依赖注入
- Golang依赖注入:反射与生成方案详解
- 441浏览 收藏
-
- Golang · Go教程 | 8小时前 |
- Golangstrings库常用方法全解析
- 372浏览 收藏
-
- Golang · Go教程 | 8小时前 | 反射 结构体 reflect.Type reflect.Value 动态赋值
- Golang反射实现动态类型赋值与创建方法
- 116浏览 收藏
-
- Golang · Go教程 | 8小时前 |
- Golang微服务监控实现方法
- 441浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 514次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 499次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- AI Mermaid流程图
- SEO AI Mermaid 流程图工具:基于 Mermaid 语法,AI 辅助,自然语言生成流程图,提升可视化创作效率,适用于开发者、产品经理、教育工作者。
- 501次使用
-
- 搜获客【笔记生成器】
- 搜获客笔记生成器,国内首个聚焦小红书医美垂类的AI文案工具。1500万爆款文案库,行业专属算法,助您高效创作合规、引流的医美笔记,提升运营效率,引爆小红书流量!
- 493次使用
-
- iTerms
- iTerms是一款专业的一站式法律AI工作台,提供AI合同审查、AI合同起草及AI法律问答服务。通过智能问答、深度思考与联网检索,助您高效检索法律法规与司法判例,告别传统模板,实现合同一键起草与在线编辑,大幅提升法律事务处理效率。
- 519次使用
-
- TokenPony
- TokenPony是讯盟科技旗下的AI大模型聚合API平台。通过统一接口接入DeepSeek、Kimi、Qwen等主流模型,支持1024K超长上下文,实现零配置、免部署、极速响应与高性价比的AI应用开发,助力专业用户轻松构建智能服务。
- 559次使用
-
- 迅捷AIPPT
- 迅捷AIPPT是一款高效AI智能PPT生成软件,一键智能生成精美演示文稿。内置海量专业模板、多样风格,支持自定义大纲,助您轻松制作高质量PPT,大幅节省时间。
- 490次使用
-
- 浅析Golang切片截取功能与C++的vector区别
- 2022-12-23 496浏览
-
- Redis集合类型使用说明
- 2023-01-20 153浏览
-
- Golang中slice删除元素的性能对比
- 2022-12-24 484浏览
-
- Golang切片删除指定元素的三种方法对比
- 2023-01-07 464浏览
-
- Golang实现文件夹的创建与删除的方法详解
- 2022-12-24 253浏览