Go數(shù)據(jù)結(jié)構(gòu)之HeapMap實(shí)現(xiàn)指定Key刪除堆
堆(Heap)
堆(Heap),又稱為優(yōu)先隊(duì)列(Priority Queue)。盡管名為優(yōu)先隊(duì)列,但堆并不是隊(duì)列。在隊(duì)列中,我們可以進(jìn)行的操作是向隊(duì)列中添加元素和按照元素進(jìn)入隊(duì)列的順序取出元素。而在堆中,我們不是按照元素進(jìn)入隊(duì)列的先后順序,而是按照元素的優(yōu)先級(jí)取出元素。
問題背景
在Linux內(nèi)核中,調(diào)度器根據(jù)各個(gè)進(jìn)程的優(yōu)先級(jí)來進(jìn)行程序的執(zhí)行調(diào)度。在操作系統(tǒng)運(yùn)行時(shí),通常會(huì)有很多個(gè)不同的進(jìn)程,各自優(yōu)先級(jí)也不相同。調(diào)度器的作用是讓優(yōu)先級(jí)高的進(jìn)程得到優(yōu)先執(zhí)行,而優(yōu)先級(jí)較低的則需要等待。堆是一種適用于實(shí)現(xiàn)這種調(diào)度器的數(shù)據(jù)結(jié)構(gòu)。需要提一下,現(xiàn)在Linux內(nèi)核的調(diào)度器使用的是基于紅黑樹的CFS(Completely Fair Scheduler)。
二叉堆的概念
我們常用的二叉堆是一顆任意節(jié)點(diǎn)的優(yōu)先級(jí)不小于其子節(jié)點(diǎn)的完全二叉樹。
完全二叉樹的定義如下:
- 若設(shè)二叉樹的高度為h,除第h層外,其它各層(1~h-1)的結(jié)點(diǎn)數(shù)都達(dá)到最大個(gè)數(shù),第h層從右向左連續(xù)缺若干結(jié)點(diǎn),這就是完全二叉樹。
比如下圖就是一顆完全二叉樹:
10
/ \
15 30
/ \ / \
40 50 100 40現(xiàn)在假設(shè)保存的數(shù)值越小的節(jié)點(diǎn)的優(yōu)先級(jí)越高,那么上圖就是一個(gè)堆。我們將任意節(jié)點(diǎn)不大于其子節(jié)點(diǎn)的堆叫做最小堆或小根堆,將任意節(jié)點(diǎn)不小于其子節(jié)點(diǎn)的堆叫做最大堆或大根堆。因此,上圖就是一個(gè)小根堆。

優(yōu)先級(jí)隊(duì)列的實(shí)現(xiàn)
通過使用Go語言中的container/heap包,我們可以輕松地實(shí)現(xiàn)一個(gè)優(yōu)先級(jí)隊(duì)列。這個(gè)隊(duì)列可以用于解決許多問題,如任務(wù)調(diào)度、事件處理等。通過設(shè)置每個(gè)項(xiàng)的優(yōu)先級(jí),我們可以確保在處理隊(duì)列時(shí)按照指定的順序進(jìn)行操作。
Item
通過定義Item結(jié)構(gòu)體來表示優(yōu)先級(jí)隊(duì)列中的項(xiàng)。每個(gè)項(xiàng)具有值(value)和優(yōu)先級(jí)(priority)。index表示項(xiàng)在優(yōu)先級(jí)隊(duì)列中的索引。
// Item represents an item in the priority queue.
type Item struct {
value int // 項(xiàng)的值。
priority int // 項(xiàng)的優(yōu)先級(jí)。
index int // 項(xiàng)在隊(duì)列中的索引。
}PriorityQueue
PriorityQueue是一個(gè)切片類型,實(shí)現(xiàn)了heap.Interface接口。它提供了用于操作優(yōu)先級(jí)隊(duì)列的方法,如插入、刪除和修改。
Len方法返回優(yōu)先級(jí)隊(duì)列的長度。
Less方法比較兩個(gè)項(xiàng)的優(yōu)先級(jí)。
Swap方法交換兩個(gè)項(xiàng)在優(yōu)先級(jí)隊(duì)列中的位置。
Push方法向優(yōu)先級(jí)隊(duì)列中添加一個(gè)項(xiàng)。
Pop方法移除并返回優(yōu)先級(jí)隊(duì)列中的最小項(xiàng)。
Update方法用于修改項(xiàng)的優(yōu)先級(jí)并更新其在優(yōu)先級(jí)隊(duì)列中的位置。
// PriorityQueue 實(shí)現(xiàn)了 heap.Interface 接口。
type PriorityQueue []*Item
// Len 返回優(yōu)先級(jí)隊(duì)列的長度。
func (pq PriorityQueue) Len() int {
return len(pq)
}
// Less 比較優(yōu)先級(jí)隊(duì)列中的兩個(gè)項(xiàng)。
func (pq PriorityQueue) Less(i, j int) bool {
return pq[i].priority < pq[j].priority
}
// Swap 交換優(yōu)先級(jí)隊(duì)列中的兩個(gè)項(xiàng)。
func (pq PriorityQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
pq[i].index = i
pq[j].index = j
}
// Push 向優(yōu)先級(jí)隊(duì)列中添加一個(gè)項(xiàng)。
func (pq *PriorityQueue) Push(x interface{}) {
item := x.(*Item)
item.index = len(*pq)
*pq = append(*pq, item)
}
// Pop 移除并返回優(yōu)先級(jí)隊(duì)列中的最小項(xiàng)。
func (pq *PriorityQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
old[n-1] = nil // 避免內(nèi)存泄漏
item.index = -1
*pq = old[0 : n-1]
return item
}
// Update 修改項(xiàng)的優(yōu)先級(jí)并更新其在優(yōu)先級(jí)隊(duì)列中的位置。
func (pq *PriorityQueue) Update(item *Item, value, priority int) {
item.value = value
item.priority = priority
heap.Fix(pq, item.index)
}改進(jìn)
但是我們經(jīng)常有一種場景,需要堆的快速求最值的性質(zhì),又需要能夠支持快速的訪問元素,特別是刪除元素。 如果我們要查找堆中的某個(gè)元素,需要遍歷一遍。非常麻煩。
比如延遲任務(wù)的場景,我們可以使用堆對(duì)任務(wù)的到期時(shí)間戳進(jìn)行排序,從而實(shí)現(xiàn)到期任務(wù)自動(dòng)執(zhí)行,但是它沒辦法支持刪除一個(gè)延遲任務(wù)的需求。
HeapMap
一種能夠快速隨機(jī)訪問元素的數(shù)據(jù)結(jié)構(gòu)是哈希表。使用哈希表實(shí)現(xiàn)的map可以在O(1)的時(shí)間復(fù)雜度下進(jìn)行隨機(jī)訪問。
另外,堆結(jié)構(gòu)可以在O(log(n))的時(shí)間復(fù)雜度下刪除元素,前提是知道要?jiǎng)h除的元素的下標(biāo)。因此,我們可以將這兩個(gè)數(shù)據(jù)結(jié)構(gòu)結(jié)合起來使用。使用哈希表記錄堆中每個(gè)元素的下標(biāo),同時(shí)使用堆來獲取最值元素。
// PriorityQueue facilitates queue of Items, providing Push, Pop, and
// PopByKey convenience methods. The ordering (priority) is an int64 value
// with the smallest value is the highest priority. PriorityQueue maintains both
// an internal slice for the queue as well as a map of the same items with their
// keys as the index. This enables users to find specific items by key. The map
// must be kept in sync with the data slice.
// See https://golang.org/pkg/container/heap/#example__priorityQueue
type PriorityQueue struct {
// data is the internal structure that holds the queue, and is operated on by
// heap functions
data queue
// dataMap represents all the items in the queue, with unique indexes, used
// for finding specific items. dataMap is kept in sync with the data slice
dataMap map[string]*Item
// lock is a read/write mutex, and used to facilitate read/write locks on the
// data and dataMap fields
lock sync.RWMutex
}
// queue is the internal data structure used to satisfy heap.Interface. This
// prevents users from calling Pop and Push heap methods directly
type queue []*Item
// Item is something managed in the priority queue
type Item struct {
// Key is a unique string used to identify items in the internal data map
Key string
// Value is an unspecified type that implementations can use to store
// information
Value interface{}
// Priority determines ordering in the queue, with the lowest value being the
// highest priority
Priority int64
// index is an internal value used by the heap package, and should not be
// modified by any consumer of the priority queue
index int
}在PriorityQueue中定義一個(gè)dataMap
dataMap是一個(gè)用于存儲(chǔ)隊(duì)列中的項(xiàng)的映射表,它的好處是可以根據(jù)項(xiàng)的鍵快速地查找到對(duì)應(yīng)的項(xiàng)。 在PriorityQueue中,有一個(gè)數(shù)據(jù)切片data,用于存儲(chǔ)隊(duì)列中的項(xiàng),并且用一個(gè)索引值index來表示項(xiàng)在切片中的位置。
dataMap則以項(xiàng)的鍵作為索引,將項(xiàng)的指針映射到該鍵上。
使用dataMap的好處是可以快速地根據(jù)鍵找到對(duì)應(yīng)的項(xiàng),而不需要遍歷整個(gè)切片。這對(duì)于需要頻繁查找和修改項(xiàng)的場景非常重要,可以提高代碼的效率。
如果沒有dataMap,想要根據(jù)鍵找到對(duì)應(yīng)的項(xiàng)則需要遍歷整個(gè)切片進(jìn)行查找,時(shí)間復(fù)雜度將為O(n)。而使用dataMap可以將查找的時(shí)間復(fù)雜度降低到O(1),提高代碼的性能。
另外,需要注意的是dataMap必須與data切片保持同步,即在對(duì)切片進(jìn)行修改時(shí),需要同時(shí)更新dataMap,保持兩者的一致性。否則,在使用dataMap時(shí)會(huì)出現(xiàn)不一致的情況,導(dǎo)致錯(cuò)誤的結(jié)果。因此,在使用PriorityQueue時(shí),需要確保維護(hù)dataMap和data切片的一致性。
push
在Push時(shí)需要保證Key值唯一
func (pq *PriorityQueue) Push(i *Item) error {
if i == nil || i.Key == "" {
return errors.New("error adding item: Item Key is required")
}
pq.lock.Lock()
defer pq.lock.Unlock()
if _, ok := pq.dataMap[i.Key]; ok {
return ErrDuplicateItem
}
// Copy the item value(s) so that modifications to the source item does not
// affect the item on the queue
clone, err := copystructure.Copy(i)
if err != nil {
return err
}
pq.dataMap[i.Key] = clone.(*Item)
heap.Push(&pq.data, clone)
return nil
}pop
PopByKey方法可以根據(jù)Key查找并移除對(duì)應(yīng)的元素
// PopByKey searches the queue for an item with the given key and removes it
// from the queue if found. Returns nil if not found. This method must fix the
// queue after removing any key.
func (pq *PriorityQueue) PopByKey(key string) (*Item, error) {
pq.lock.Lock()
defer pq.lock.Unlock()
item, ok := pq.dataMap[key]
if !ok {
return nil, nil
}
// Remove the item the heap and delete it from the dataMap
itemRaw := heap.Remove(&pq.data, item.index)
delete(pq.dataMap, key)
if itemRaw != nil {
if i, ok := itemRaw.(*Item); ok {
return i, nil
}
}
return nil, nil
}測試用例
func main() {
// 測試Push方法
pq := &PriorityQueue{
data: queue{},
dataMap: make(map[string]*Item),
}
item := &Item{
Key: "key1",
Value: "value1",
Priority: 1,
index: 0,
}
err := pq.Push(item)
if err != nil {
fmt.Println("Push error:", err)
} else {
fmt.Println("Push success")
}
// 測試Pop方法
poppedItem, err := pq.Pop()
if err != nil {
fmt.Println("Pop error:", err)
} else {
fmt.Println("Popped item key:", poppedItem.Key)
}
// 測試PopByKey方法
item2 := &Item{
Key: "key2",
Value: "value2",
Priority: 2,
index: 1,
}
pq.Push(item2)
poppedItemByKey, err := pq.PopByKey("key2")
if err != nil {
fmt.Println("PopByKey error:", err)
} else if poppedItemByKey == nil {
fmt.Println("Item not found")
} else {
fmt.Println("Popped item by key:", poppedItemByKey.Key)
}
// 測試Len方法
item3 := &Item{
Key: "key3",
Value: "value3",
Priority: 3,
index: 2,
}
pq.Push(item3)
length := pq.Len()
fmt.Println("Queue length:", length)
}以上就是Go數(shù)據(jù)結(jié)構(gòu)之HeapMap實(shí)現(xiàn)指定Key刪除堆的詳細(xì)內(nèi)容,更多關(guān)于Go HeapMap指定Key刪除堆的資料請關(guān)注腳本之家其它相關(guān)文章!
- 淺析Go語言中的map數(shù)據(jù)結(jié)構(gòu)是如何實(shí)現(xiàn)的
- Golang對(duì)struct字段重新排序優(yōu)化數(shù)據(jù)結(jié)構(gòu)性能實(shí)踐
- Golang實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)Stack(堆棧)的示例詳解
- Golang迭代如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解
- 詳解如何在Go語言中循環(huán)數(shù)據(jù)結(jié)構(gòu)
- Go語言數(shù)據(jù)結(jié)構(gòu)之二叉樹可視化詳解
- Go 數(shù)據(jù)結(jié)構(gòu)之堆排序示例詳解
- Go實(shí)現(xiàn)List、Set、Stack、Deque等數(shù)據(jù)結(jié)構(gòu)的操作方法
相關(guān)文章
Go如何優(yōu)雅的關(guān)閉goroutine協(xié)程
本文將介紹首先為什么需要主動(dòng)關(guān)閉goroutine,并介紹如何在Go語言中關(guān)閉goroutine的常見套路,包括傳遞終止信號(hào)和協(xié)程內(nèi)部捕捉終止信號(hào),之后,文章列舉了需要主動(dòng)關(guān)閉協(xié)程運(yùn)行的常見場景,希望通過本文的介紹,讀者能夠掌握如何在適當(dāng)?shù)臅r(shí)候關(guān)閉goroutine2023-05-05
Golang實(shí)現(xiàn)帶優(yōu)先級(jí)的select
這篇文章主要為大家詳細(xì)介紹了如何在Golang中實(shí)現(xiàn)帶優(yōu)先級(jí)的select,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Golang有一定的幫助,需要的可以參考一下2023-04-04
Golang創(chuàng)建第一個(gè)web項(xiàng)目(Gin+Gorm)
本文主要介紹了Golang創(chuàng)建第一個(gè)web項(xiàng)目(Gin+Gorm),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
golang協(xié)程設(shè)計(jì)及調(diào)度原理
這篇文章主要介紹了golang協(xié)程設(shè)計(jì)及調(diào)度原理,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-06-06
golang使用redis實(shí)現(xiàn)全文搜索功能詳解
這篇文章主要為大家詳細(xì)介紹了golang如何使用redis實(shí)現(xiàn)全文搜索功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-02-02
在Go程序中實(shí)現(xiàn)服務(wù)器重啟的方法
這篇文章主要介紹了在Go程序中實(shí)現(xiàn)服務(wù)器重啟的方法,由于很多人盲目崇拜谷歌"親爹",Go語言在國內(nèi)有著不尋常的人氣,需要的朋友可以參考下2015-06-06

