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

優(yōu)先級隊列的實現(xiàn)
通過使用Go語言中的container/heap包,我們可以輕松地實現(xiàn)一個優(yōu)先級隊列。這個隊列可以用于解決許多問題,如任務調(diào)度、事件處理等。通過設置每個項的優(yōu)先級,我們可以確保在處理隊列時按照指定的順序進行操作。
Item
通過定義Item結構體來表示優(yōu)先級隊列中的項。每個項具有值(value)和優(yōu)先級(priority)。index表示項在優(yōu)先級隊列中的索引。
// Item represents an item in the priority queue.
type Item struct {
value int // 項的值。
priority int // 項的優(yōu)先級。
index int // 項在隊列中的索引。
}PriorityQueue
PriorityQueue是一個切片類型,實現(xiàn)了heap.Interface接口。它提供了用于操作優(yōu)先級隊列的方法,如插入、刪除和修改。
Len方法返回優(yōu)先級隊列的長度。
Less方法比較兩個項的優(yōu)先級。
Swap方法交換兩個項在優(yōu)先級隊列中的位置。
Push方法向優(yōu)先級隊列中添加一個項。
Pop方法移除并返回優(yōu)先級隊列中的最小項。
Update方法用于修改項的優(yōu)先級并更新其在優(yōu)先級隊列中的位置。
// PriorityQueue 實現(xiàn)了 heap.Interface 接口。
type PriorityQueue []*Item
// Len 返回優(yōu)先級隊列的長度。
func (pq PriorityQueue) Len() int {
return len(pq)
}
// Less 比較優(yōu)先級隊列中的兩個項。
func (pq PriorityQueue) Less(i, j int) bool {
return pq[i].priority < pq[j].priority
}
// Swap 交換優(yōu)先級隊列中的兩個項。
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)先級隊列中添加一個項。
func (pq *PriorityQueue) Push(x interface{}) {
item := x.(*Item)
item.index = len(*pq)
*pq = append(*pq, item)
}
// Pop 移除并返回優(yōu)先級隊列中的最小項。
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 修改項的優(yōu)先級并更新其在優(yōu)先級隊列中的位置。
func (pq *PriorityQueue) Update(item *Item, value, priority int) {
item.value = value
item.priority = priority
heap.Fix(pq, item.index)
}改進
但是我們經(jīng)常有一種場景,需要堆的快速求最值的性質(zhì),又需要能夠支持快速的訪問元素,特別是刪除元素。 如果我們要查找堆中的某個元素,需要遍歷一遍。非常麻煩。
比如延遲任務的場景,我們可以使用堆對任務的到期時間戳進行排序,從而實現(xiàn)到期任務自動執(zhí)行,但是它沒辦法支持刪除一個延遲任務的需求。
HeapMap
一種能夠快速隨機訪問元素的數(shù)據(jù)結構是哈希表。使用哈希表實現(xiàn)的map可以在O(1)的時間復雜度下進行隨機訪問。
另外,堆結構可以在O(log(n))的時間復雜度下刪除元素,前提是知道要刪除的元素的下標。因此,我們可以將這兩個數(shù)據(jù)結構結合起來使用。使用哈希表記錄堆中每個元素的下標,同時使用堆來獲取最值元素。
// 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中定義一個dataMap
dataMap是一個用于存儲隊列中的項的映射表,它的好處是可以根據(jù)項的鍵快速地查找到對應的項。 在PriorityQueue中,有一個數(shù)據(jù)切片data,用于存儲隊列中的項,并且用一個索引值index來表示項在切片中的位置。
dataMap則以項的鍵作為索引,將項的指針映射到該鍵上。
使用dataMap的好處是可以快速地根據(jù)鍵找到對應的項,而不需要遍歷整個切片。這對于需要頻繁查找和修改項的場景非常重要,可以提高代碼的效率。
如果沒有dataMap,想要根據(jù)鍵找到對應的項則需要遍歷整個切片進行查找,時間復雜度將為O(n)。而使用dataMap可以將查找的時間復雜度降低到O(1),提高代碼的性能。
另外,需要注意的是dataMap必須與data切片保持同步,即在對切片進行修改時,需要同時更新dataMap,保持兩者的一致性。否則,在使用dataMap時會出現(xiàn)不一致的情況,導致錯誤的結果。因此,在使用PriorityQueue時,需要確保維護dataMap和data切片的一致性。
push
在Push時需要保證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查找并移除對應的元素
// 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ù)結構之HeapMap實現(xiàn)指定Key刪除堆的詳細內(nèi)容,更多關于Go HeapMap指定Key刪除堆的資料請關注腳本之家其它相關文章!
相關文章
Go如何優(yōu)雅的關閉goroutine協(xié)程
本文將介紹首先為什么需要主動關閉goroutine,并介紹如何在Go語言中關閉goroutine的常見套路,包括傳遞終止信號和協(xié)程內(nèi)部捕捉終止信號,之后,文章列舉了需要主動關閉協(xié)程運行的常見場景,希望通過本文的介紹,讀者能夠掌握如何在適當?shù)臅r候關閉goroutine2023-05-05
Golang實現(xiàn)帶優(yōu)先級的select
這篇文章主要為大家詳細介紹了如何在Golang中實現(xiàn)帶優(yōu)先級的select,文中的示例代碼講解詳細,對我們學習Golang有一定的幫助,需要的可以參考一下2023-04-04
Golang創(chuàng)建第一個web項目(Gin+Gorm)
本文主要介紹了Golang創(chuàng)建第一個web項目(Gin+Gorm),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-06-06

