今天在准备《秘而不宣》系列下一篇文章时,思绪飘散了,突然想到使用 Heap 的功能再加 HashTable (Map) 的功能,可以构造一种新的数据结构,然后把我聚合程序中的数据聚合数据结构替换掉,总之思绪翩翩。然后在网上搜了一下,这种数据结构其实早就有了,名字叫 HeapMap
。
HeapMap
(也叫做 PriorityMap
) 是一种结合了堆和哈希映射的数据结构,常用于需要按键排序并进行高效查找的场景。它可以在优先级队列的基础上,使用哈希映射来提供快速访问和更新。HeapMap
在实现过程中利用堆的有序性和哈希表的快速查找能力,以支持按键排序和常数时间查找。
Go 语言支付 Rob Pike 在他的 Rob Pike's 5 Rules of Programming 第 5 条就指出:
- Data dominates. If you've chosen the right data structures and organized things well, the algorithms will almost always be self-evident. Data structures, not algorithms, are central to programming.
数据为王。如果你选择了合适的数据结构并进行了良好的组织,算法通常会变得显而易见。编程的核心在于数据结构,而非算法。
所以,如果在合适的场景下,针对它的特点,使用 HeapMap 会取得事半功倍的效果。
HeapMap
的主要特点
- 堆的特点:
HeapMap
内部通过堆来维护键的顺序,可以快速获取最小或最大键。堆提供了插入和删除堆顶元素的O(log n)
时间复杂度。 - 哈希映射的特点:
HeapMap
同时使用哈希映射以支持快速查找。哈希映射的查找、插入、删除等操作在理想情况下时间复杂度为O(1)
。 - 用途:
HeapMap
适合需要频繁按键排序和快速查找的场景,比如带有优先级的缓存、调度系统、任务优先队列等。
HeapMap
的基本结构
- 堆(Heap):用来维持按键的顺序,堆可以是最小堆或最大堆,根据具体需求决定。
- 哈希映射(Map):用来存储每个键值对,并支持通过键快速查找元素。
你使用一个 container/heap
+ map
很容易实现一个 HeapMap
, 其实我们没必要自己去写一个重复的轮子了,网上其他语言比如 Rust、Java 都有现成的实现,Go 语言中也有一个很好的实现:nemars/heapmap
HeapMap
的实现
nemars/heapmap
这个库是去年增加到 github 中的,我是第一个 star 它的人。我们看看它是怎么实现的。
结构体定义
|
|
Entry
代表这个数据结构中的一个节点 (元素、条目) , 它包含 key、value 值,还有优先级,index 记录它在堆的实现数组中的索引。
heapmap
代表 HeapMap
的实现,它包含两个字段,第一个字段其实就是 Heap
的实现,为了方便实现泛型,它就自己实现了一个堆。第二个字段就是一个 map 对象了。
典型的方法
数据结构定义清楚了,那就就可以实现它的方法了。它实现了一些便利的方法,我们值关注几个实现就好了。
Len 方法
|
|
读取h
字段或者m
字段的长度都可以。
Peek 方法
返回root元素。
最小堆就是返回最小的元素,最大堆就是返回最大的元素。
|
|
Pop 方法
弹出root元素。
|
|
注意涉及到元素的删除操作,要同时删除 map 中的元素。
Push 方法 (Set 方法)
其实作者没有实现 Push 方法,而是使用Set 方法来实现的。
|
|
Set方法有两个功能。如果元素的Key已经存在,那么就是更新元素,并且根据优先级进行调整。
如果元素的Key不存在,那么就是插入元素。
Get 方法
Get 方法就是获取任意的元素。
|
|
有一点你需要注意的是,这个数据结构不是线程安全的,如果你需要线程安全的话,你可以使用 sync.Mutex
/sync.RWMutex
来保护它。