详解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 相关文章推荐
搞笑的程序猿:看看你是哪种Python程序员
Jun 12 Python
Python 实现文件的全备份和差异备份详解
Dec 27 Python
python实现猜单词小游戏
May 22 Python
python把转列表为集合的方法
Jun 28 Python
pandas实现to_sql将DataFrame保存到数据库中
Jul 03 Python
python实现二分类的卡方分箱示例
Nov 22 Python
django 前端页面如何实现显示前N条数据
Mar 16 Python
django实现模板中的字符串文字和自动转义
Mar 31 Python
Python批量安装卸载1000个apk的方法
Apr 10 Python
Python实现SMTP邮件发送
Jun 16 Python
浅析Python 责任链设计模式
Sep 11 Python
python如何实现递归转非递归
Feb 25 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中替换字符串中的空格为逗号','的方法
2014/06/09 PHP
Codeigniter控制器controller继承问题实例分析
2016/01/19 PHP
php的常量和变量实例详解
2017/06/27 PHP
Laravel框架中Blade模板的用法示例
2017/08/30 PHP
phpStudy vscode 搭建debug调试的教程详解
2020/07/28 PHP
帮助避免错误的Javascript陷阱清单
2009/05/31 Javascript
JavaScript 学习笔记 Black.Caffeine 09.11.28
2009/11/30 Javascript
收集的一些Array及String原型对象的扩展实现代码
2010/12/05 Javascript
jWiard 基于JQuery的强大的向导控件介绍
2011/10/28 Javascript
Javascript级联下拉菜单以及AJAX数据验证核心代码
2013/05/10 Javascript
jQuery Ajax异步处理Json数据详解
2013/11/05 Javascript
Js+php实现异步拖拽上传文件
2015/06/23 Javascript
javascript实现类似百度分享功能的方法
2015/07/27 Javascript
JS延时提示框实现方法详解
2015/11/26 Javascript
Bootstrap4一次重大更新 几乎涉及每行代码
2016/05/16 Javascript
jquery 判断selection range 是否在容器中的简单实例
2016/08/02 Javascript
jquery.validate表单验证插件使用方法解析
2016/11/07 Javascript
js实现上下左右弹框划出效果
2017/03/08 Javascript
微信小程序实现topBar底部选择栏效果
2018/07/20 Javascript
迅速了解一下ES10中Object.fromEntries的用法使用
2019/03/05 Javascript
bootstrap-table实现表头固定以及列固定的方法示例
2019/03/07 Javascript
vue-router源码之history类的浅析
2019/05/21 Javascript
帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)
2019/08/23 Javascript
Vue实现腾讯云点播视频上传功能的实现代码
2020/08/17 Javascript
[58:18]2018DOTA2亚洲邀请赛3月29日 小组赛B组 iG VS Mineski
2018/03/30 DOTA
python中的实例方法、静态方法、类方法、类变量和实例变量浅析
2014/04/26 Python
python求众数问题实例
2014/09/26 Python
Python实现从脚本里运行scrapy的方法
2015/04/07 Python
举例讲解Python中的死锁、可重入锁和互斥锁
2015/11/05 Python
修复 Django migration 时遇到的问题解决
2018/06/14 Python
Python提取特定时间段内数据的方法实例
2019/04/01 Python
音频处理 windows10下python三方库librosa安装教程
2020/06/20 Python
python re.match()用法相关示例
2021/01/27 Python
《盲人摸象》教学反思
2014/02/16 职场文书
党员自我对照检查材料
2014/08/19 职场文书
综合素质评价自我评价
2015/03/06 职场文书