详解Golang如何实现支持随机删除元素的堆


Posted in Python onSeptember 23, 2022

背景

堆是一种非常常用的数据结构,它能够支持在O(1)的时间复杂度获取到最大值(或最小值),因此我们经常在需要求最值的场景使用它。

然而普通堆它有一个缺点,它没办法快速的定位一个元素,因此它也没办法快速删除一个堆中元素,需要遍历整个堆去查询目标元素,时间复杂度是O(n),因为堆的结构在逻辑上是这样的:

详解Golang如何实现支持随机删除元素的堆

在实际实现中一般是使用一个数组来模拟:

详解Golang如何实现支持随机删除元素的堆

也就是除了最大值(大顶堆)或最小值(小顶堆),其他元素其实是没有排序的,因此没办法通过二分查找的方式快速定位。

但是我们经常有一种场景,需要堆的快速求最值的性质,又需要能够支持快速的随机访问元素,特别是删除元素。

比如延迟任务的场景,我们可以使用堆对任务的到期时间戳进行排序,从而实现到期任务自动执行,但是它没办法支持随机删除一个延迟任务的需求。

下面将介绍一种支持O(log(n))随机删除元素的堆。

原理

数据结构

一种能够快速随机访问元素的数据结构是map,map如果是哈希表实现则能够在O(1)的复杂度下随机访问任何元素,而heap在知道要删除的元素的下标的情况下,也可以在O(log(n))的复杂度删除一个元素。因此我们可以结合这两个数据结构,使用map来记录heap中每个元素的下标,使用heap来获取最值。

比如对于上面的堆,我们首先给每个元素添加一个Key,这样我们才能够使用map:

详解Golang如何实现支持随机删除元素的堆

然后我们使用map记录每个key对应的下标:

详解Golang如何实现支持随机删除元素的堆

随机访问

这时候比如我们最简单的随机访问一个元素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随机删除元素内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Hadoop中的Python框架的使用指南
Apr 22 Python
深入解析Python中的上下文管理器
Jun 28 Python
python监控键盘输入实例代码
Feb 09 Python
详解python多线程之间的同步(一)
Apr 03 Python
Python turtle绘画象棋棋盘
Aug 21 Python
python redis 批量设置过期key过程解析
Nov 26 Python
pytorch 实现张量tensor,图片,CPU,GPU,数组等的转换
Jan 13 Python
Pandas时间序列基础详解(转换,索引,切片)
Feb 26 Python
Python @property及getter setter原理详解
Mar 31 Python
python数据分析工具之 matplotlib详解
Apr 09 Python
Python json转字典字符方法实例解析
Apr 13 Python
如何用python绘制雷达图
Apr 24 Python
python中validators库的使用方法详解
Sep 23 #Python
Python pyecharts案例超市4年数据可视化分析
Aug 14 #Python
Python编写车票订购系统 Python实现快递收费系统
Aug 14 #Python
Golang Web 框架Iris安装部署
Aug 14 #Python
python manim实现排序算法动画示例
Python 操作pdf pdfplumber读取PDF写入Exce
Aug 14 #Python
Python使用plt.boxplot()函数绘制箱图、常用方法以及含义详解
Aug 14 #Python
You might like
php各种编码集详解和以及在什么情况下进行使用
2011/09/11 PHP
浅析php面向对象public private protected 访问修饰符
2013/06/30 PHP
ThinkPHP中RBAC类的四种用法分析
2014/11/24 PHP
PHP实现打包zip并下载功能
2018/06/12 PHP
50个优秀经典PHP算法大集合 附源码
2020/08/26 PHP
让回调函数 showResponse 也带上参数的代码
2007/08/13 Javascript
javascript显示隐藏层比较不错的方法分析
2008/09/30 Javascript
javascript Base类 包含基本的方法
2009/07/22 Javascript
url地址自动加#号问题说明
2010/08/21 Javascript
JavaScript学习笔记之Function对象
2015/01/22 Javascript
7个让JavaScript变得更好的注意事项
2015/01/28 Javascript
JavaScript实现按照指定长度为数字前面补零输出的方法
2015/03/19 Javascript
JS如何实现文本框随文本的长度而增长
2015/07/30 Javascript
JS实现根据用户输入分钟进行倒计时功能
2016/11/14 Javascript
js判断手机系统是android还是ios
2017/03/07 Javascript
jQuery使用zTree插件实现可拖拽的树示例
2017/09/23 jQuery
基于vue-cli配置lib-flexible + rem实现移动端自适应
2017/12/26 Javascript
JS实现动态生成html table表格的方法分析
2018/07/11 Javascript
JS实现点击按钮随机生成可拖动的不同颜色块示例
2019/01/30 Javascript
jQuery实现轮播图效果demo
2020/01/11 jQuery
javascript实现前端分页功能
2020/11/26 Javascript
[58:21]DOTA2亚洲邀请赛 4.3 突围赛 Liquid vs VGJ.T 第二场
2018/04/04 DOTA
跟老齐学Python之编写类之一创建实例
2014/10/11 Python
Windows上使用Python增加或删除权限的方法
2018/04/24 Python
tensorflow 加载部分变量的实例讲解
2018/07/27 Python
Python面向对象程序设计OOP深入分析【构造函数,组合类,工具类等】
2019/01/05 Python
Python I/O与进程的详细讲解
2019/03/08 Python
tensorflow实现对张量数据的切片操作方式
2020/01/19 Python
python numpy矩阵信息说明,shape,size,dtype
2020/05/22 Python
python 实现图像快速替换某种颜色
2020/06/04 Python
坚定理想信念心得体会
2014/03/11 职场文书
村主任群众路线个人对照检查材料
2014/09/26 职场文书
专题组织生活会发言材料
2014/10/17 职场文书
如何写辞职书
2015/02/26 职场文书
餐馆开业致辞
2015/08/01 职场文书
2016中学教师读书心得体会
2016/01/13 职场文书