這天「對」Golang牌出來搞鬼了,什麼東西都變成一對一對。小櫻想嘗試收服,但要同時把兩隻打敗才能收服不然都會封印失敗。此時最有默契的兄妹終於出現啦!!
之前在介紹「佇列」(Queue)時有提到先進先出的概念,就好比排隊,先到的人可以先進場。
但是,有時候有所謂「快速通關」的服務,所謂的快速通關,就是你比別人有更高的優先權。而每個元素有不同「優先權」的佇列,稱為「優先佇列」,不是比誰加入佇列,而是比誰的優先權高。
如果利用「陣列」來實作,那麼每次要找誰有最高的權限時就必需要把所有的元素都看一遍,移除,該元素時還要把後面的元素都往前放。當然,也可以用「鏈結串列」來實現,這樣子比較不會有移動的問題,但是,要找到最大值,也很不容易。而且,如果在過程中,還要加入新的元素,就更麻煩了!
我們可以使用「堆」(Heap)來實作優先佇列:
在實作 Priority Queue 前我們先來了解 Go 提供的 heap 套件以方便我們的實作
可以看到 Fix, Init, Pop, Push, Remove 都有以 Interface 為對象所建立的方法,因此要實現 heap 前,要先建立一個可以滿足 heap.Interface 的型態
我們先自訂一個 myHeap 來示範
- 該型態要先能滿足 sort.Interface{}
- 實作
Push(x interface{})
該方法是將 x 添加至 myHeap 的第 Len() 個索引中- 實作
Pop() interface{}
該方法是將第 Len() - 1 個元素從 myHeap 中確實移除 (必需要重新更動長度)
Golang 官方範例 (min heap)
// This example demonstrates an integer heap built using the heap interface.
// 該範例演示藉由 heap 介面建立一個「整數(最小)堆積」
package main
import (
"container/heap"
"fmt"
)
// An IntHeap is a min-heap of ints.
// 建立自訂型態
type IntHeap []int
// 實作以滿足 sort.Interface 的部分
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) {
// Push and Pop use pointer receivers because they modify the slice's length,
// not just its contents.
// Push 和 Pop 要透過指標傳值,因為會更動到 slice 的長度而不只是內容
// 解釋:
// append 過後的切片可能已經不是本來的切片
// 所以要透過指標更改 h 的值
*h = append(*h, x.(int))
}
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
// 先將要回傳的值暫存起來
x := old[n-1]
// 切片重切,因為 heap 套件在內部實作時是
// 靠切片的長度在定位
// 跟我們先前的方式不太一樣
// 所以一定要重切
*h = old[0 : n-1]
return x
}
// This example inserts several ints into an IntHeap, checks the minimum,
// and removes them in order of priority.
// 這個範例插入一些整數進入 IntHeap
// 檢查最小值並依權重將他們移除
func main() {
h := &IntHeap{2, 1, 5}
heap.Init(h)
heap.Push(h, 3)
fmt.Printf("minimum: %d\n", (*h)[0])
for h.Len() > 0 {
fmt.Printf("%d ", heap.Pop(h))
}
}
執行結果:
minimum: 1
1 2 3 5
改寫示範 (Max heap)
package main
import(
"container/heap"
"fmt"
)
type maxHeap []int
func (h maxHeap) Len() int{ return len(h) }
func (h maxHeap) Swap(i, j int){ h[i], h[j] = h[j], h[i] }
func (h maxHeap) Less(i, j int) bool{
// heap 預設是實作 min heap
// 因為我們要實作 max heap
// 所以把 less 的答案倒轉
return h[i] > h[j]
}
// Push 要實作的是就是
// 當 heap 新增元素要把新元素加入哪裡 (即 *h[Len(*h)])
func (h *maxHeap) Push(x interface{}){
*h = append(*h, x.(int))
}
// Pop 要實作的是
// 當有元素被移除時,要用哪一個元素去替補
// 通常都是拿最後一個 (即 *h[Len(*h)-1])
func (h *maxHeap) Pop() interface{}{
old := *h
tmp := old[len(*h)-1]
*h = old[0: len(*h)-1]
return tmp
}
func main(){
h := &maxHeap{-5, -1, 1, 2, 3, 4, 8}
// init 會將初始的值先做成堆
// 這樣就不用像我們之前實作時得從空陣列(切片)開始加了
heap.Init(h)
heap.Push(h, 9)
fmt.Printf("%d\n", heap.Pop(h))
fmt.Printf("%d\n", heap.Pop(h))
fmt.Printf("%d\n", heap.Pop(h))
}
執行結果:
9
8
4
其中,Priority Queue 要有什麼方法呢?
// 直接來限制使用者加進的元素
type Element struct{
Val Interface{} // 任意值
Priority float64 // 權重(利用 float64 更有彈性)
}
type PQ interface{
En(*Element) // 新增元素
De() *Element // 返回目前最大的元素
}
首先我們先創建一個 package,至於要怎麼創建?請複習:#14 套件 Package (2020) & 套件實戰(math, http, sort) | Golang魔法使
./pq/pq.go
1. 處理 heap 的部份,並宣告會用到的型態
package pq
import "container/heap"
// 用來維持套件內 heap 的運作
type pqHeap [](*Element)
func (h pqHeap) Len() int{ return len(h) }
func (h pqHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h pqHeap) Less(i, j int) bool { return h[i].Priority > h[j].Priority }
func (h *pqHeap) Push(x interface{}){
*h = append(*h, x.(*Element))
}
func (h *pqHeap) Pop() interface{}{
old := *h
tmp := old[len(*h)-1]
*h = old[0: len(*h)-1]
return tmp
}
type Element struct{
Val interface{} // 任意值
Priority float64 // 權重
}
type PQ struct{
h *pqHeap
num int // 計算 heap 內元素個數
}
2. 允許使用者建立新的priority queue 而且可以更簡單的新增 Element
func New() *PQ{
pq := new(PQ)
pq.h = &pqHeap{}
heap.Init(pq.h)
return pq
}
func NewElement(val interface{}, priority float64) *Element{
e := new(Element)
e.Val = val
e.Priority = priority
return e
}
3. 實作 En() 和 De()
func (p *PQ) En(e *Element){
heap.Push(p.h, e)
p.num++
}
func (p *PQ) De() *Element{
if p.num == 0{
return nil
}
p.num--
return heap.Pop(p.h).(*Element)
}
實際調用:
本教學採用新的套件模式 (go version >= 1.11)
預設 module 名稱是 practice ,所以要 import "practice/pq"
./lseeon28-2.go
package main
import(
"practice/pq"
"fmt"
)
func main(){
// 快速建立優先佇列
q := pq.New()
// 將值和權重利用 En() 寫入優先佇列
q.En(pq.NewElement("小櫻", 500.0))
q.En(pq.NewElement("雪兔", 400.0))
q.En(pq.NewElement("知世", 600.0))
q.En(pq.NewElement("小狼", 300.0))
q.En(pq.NewElement("小可", 100.0))
q.En(pq.NewElement("莓玲", 200.0))
// 按照權重由大到小從優先佇列移除
for e := q.De(); e != nil; e = q.De(){
fmt.Printf("%s (權重:%f)\n", e.Val.(string), e.Priority)
}
}
執行結果:
知世 (權重:600.000000)
小櫻 (權重:500.000000)
雪兔 (權重:400.000000)
小狼 (權重:300.000000)
莓玲 (權重:200.000000)
小可 (權重:100.000000)
本範例演示如何透過套件的方式,將原先提供的套件做更延伸使用
這天莓玲要做蛋糕送給小狼,但卻把廚房弄得亂七八遭
為什麼莓玲要做蛋糕給小狼吃呢?因為莓玲即將回去香港。因為小櫻捨不得莓玲離開,於是邀請莓玲到他家過夜。然後小櫻就把小可趕去知世家了
小可ㄎㄧㄤ爆:
搞什麼鬼啊,小丫頭要到家裡住居然把我趕出來
本來我還想好好教訓他們人生的大道理呢
不過算了
這裡有蛋糕吃(知世做的)
而且,又可以欣賞自己的英姿(知世拍的影片)
說實在話,我覺得我自己真帥
因為最後 day29, day30 想把劇情帶到夢境(小結局)那裡,所以中間跳了幾篇~嘿嘿~
本文圖片大多來自: