欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言利用heap實現(xiàn)優(yōu)先級隊列

 更新時間:2023年05月19日 11:32:25   作者:隨風(fēng)飄雪012  
這篇文章主要為大家詳細(xì)介紹了Go語言中heap的使用以及如何利用heap實現(xiàn)優(yōu)先級隊列的相關(guān)資料,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

1. heap使用

在go語言的標(biāo)準(zhǔn)庫container中,實現(xiàn)了三中數(shù)據(jù)類型:heap,list,ring,list在前面一篇文章中已經(jīng)寫了,現(xiàn)在要寫的是heap(堆)的源碼剖析。

首先,學(xué)會怎么使用heap,第一步當(dāng)然是導(dǎo)入包了,代碼如下:

package main

import (
	"container/heap"
	"fmt"
)

這個堆使用的數(shù)據(jù)結(jié)構(gòu)是最小二叉樹,即根節(jié)點比左邊子樹和右邊子樹的所有值都小。源碼里面只是實現(xiàn)了一個接口,它的定義如下:

type Interface interface {
	sort.Interface
	Push(x interface{}) // add x as element Len()
	Pop() interface{}   // remove and return element Len() - 1.
}

從這個接口可以看出,其繼承了sort.Interface接口,那么sort.Interface的定義是什么呢?源碼如下:

type Interface interface {
	// Len is the number of elements in the collection.
	Len() int
	// Less reports whether the element with
	// index i should sort before the element with index j.
	Less(i, j int) bool
	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}

也就是說,我們要使用go標(biāo)準(zhǔn)庫給我們提供的heap,那么必須自己實現(xiàn)這些接口定義的方法,需要實現(xiàn)的方法如下:

  • Len() int
  • Less(i, j int) bool
  • Swap(i, j int)
  • Push(x interface{})
  • Pop() interface{}

實現(xiàn)了這五個方法的數(shù)據(jù)類型才能使用go標(biāo)準(zhǔn)庫給我們提供的heap,下面簡單示例為定義一個IntHeap類型,并實現(xiàn)上面五個方法。

type IntHeap []int  // 定義一個類型

func (h IntHeap) Len() int { return len(h) }  // 綁定len方法,返回長度
func (h IntHeap) Less(i, j int) bool {  // 綁定less方法
	return h[i] < h[j]  // 如果h[i]<h[j]生成的就是小根堆,如果h[i]>h[j]生成的就是大根堆
}
func (h IntHeap) Swap(i, j int) {  // 綁定swap方法,交換兩個元素位置
	h[i], h[j] = h[j], h[i]
}

func (h *IntHeap) Pop() interface{} {  // 綁定pop方法,從最后拿出一個元素并返回
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x
}

func (h *IntHeap) Push(x interface{}) {  // 綁定push方法,插入新元素
	*h = append(*h, x.(int))
}

針對IntHeap實現(xiàn)了這五個方法之后,我們就可以使用heap了,下面是具體使用方法:

func main() {
	h := &IntHeap{2, 1, 5, 6, 4, 3, 7, 9, 8, 0}  // 創(chuàng)建slice
	heap.Init(h)  // 初始化heap
	fmt.Println(*h)
	fmt.Println(heap.Pop(h))  // 調(diào)用pop
	heap.Push(h, 6)  // 調(diào)用push
	fmt.Println(*h)
	for len(*h) > 0 {
		fmt.Printf("%d ", heap.Pop(h))
	}
}

輸出結(jié)果:

[0 1 3 6 2 5 7 9 8 4]
0
[1 2 3 6 4 5 7 9 8 6]
1 2 3 4 5 6 6 7 8 9 

上面就是heap的使用了。

2. heap提供的方法

heap提供的方法不多,具體如下:

h := &IntHeap{3, 8, 6}  // 創(chuàng)建IntHeap類型的原始數(shù)據(jù)
func Init(h Interface)  // 對heap進行初始化,生成小根堆(或大根堆)
func Push(h Interface, x interface{})  // 往堆里面插入內(nèi)容
func Pop(h Interface) interface{}  // 從堆頂pop出內(nèi)容
func Remove(h Interface, i int) interface{}  // 從指定位置刪除數(shù)據(jù),并返回刪除的數(shù)據(jù)
func Fix(h Interface, i int)  // 從i位置數(shù)據(jù)發(fā)生改變后,對堆再平衡,優(yōu)先級隊列使用到了該方法

3. heap源碼剖析

heap的內(nèi)部實現(xiàn),是使用最小(最大)堆,索引排序從根節(jié)點開始,然后左子樹,右子樹的順序方式。 內(nèi)部實現(xiàn)的down和up分別表示對堆中的某個元素向下保證最小(最大)堆和向上保證最小(最大)堆。

當(dāng)往堆中插入一個元素的時候,這個元素插入到最右子樹的最后一個節(jié)點中,然后調(diào)用up向上保證最小(最大)堆。

當(dāng)要從堆中推出一個元素的時候,先吧這個元素和右子樹最后一個節(jié)點交換,然后彈出最后一個節(jié)點,然后對root調(diào)用down,向下保證最小(最大)堆。

好了,開始分析源碼:

首先,在使用堆之前,必須調(diào)用它的Init方法,初始化堆,生成小根(大根)堆。Init方法源碼如下:

// A heap must be initialized before any of the heap operations
// can be used. Init is idempotent with respect to the heap invariants
// and may be called whenever the heap invariants may have been invalidated.
// Its complexity is O(n) where n = h.Len().
//
func Init(h Interface) {
	// heapify
	n := h.Len()  // 獲取數(shù)據(jù)的長度
	for i := n/2 - 1; i >= 0; i-- {  // 從長度的一半開始,一直到第0個數(shù)據(jù),每個位置都調(diào)用down方法,down方法實現(xiàn)的功能是保證從該位置往下保證形成堆
		down(h, i, n)
	}
}

接下來看down的源碼:

func down(h Interface, i0, n int) bool {
	i := i0  // 中間變量,第一次存儲的是需要保證往下需要形成堆的節(jié)點位置
	for {  // 死循環(huán)
		j1 := 2*i + 1  // i節(jié)點的左子孩子
		if j1 >= n || j1 < 0 { // j1 < 0 after int overflow  // 保證其左子孩子沒有越界
			break
		}
		j := j1 // left child  // 中間變量j先賦值為左子孩子,之后j將被賦值為左右子孩子中最?。ù螅┑囊粋€孩子的位置
		if j2 := j1 + 1; j2 < n && !h.Less(j1, j2) {
			j = j2 // = 2*i + 2  // right child
		}  // 這之后,j被賦值為兩個孩子中的最?。ù螅┖⒆拥奈恢茫ㄗ钚』蜃畲笥蒐ess中定義的決定)
		if !h.Less(j, i) {
			break
		}  // 若j大于(小于)i,則終止循環(huán)
		h.Swap(i, j)  // 否則交換i和j位置的值
		i = j  // 令i=j,繼續(xù)循環(huán),保證j位置的子數(shù)是堆結(jié)構(gòu)
	}
	return i > i0
}

這是建立堆的核心代碼,其實,down并不能完全保證從某個節(jié)點往下每個節(jié)點都能保持堆的特性,只能保證某個節(jié)點的值如果不滿足堆的性質(zhì),則將該值與其孩子交換,直到該值放到適合的位置,保證該值及其兩個子孩子滿足堆的性質(zhì)。

但是,如果是通過Init循環(huán)調(diào)用down將能保證初始化后所有的節(jié)點都保持堆的特性,這是因為循環(huán)開始的i := n/2 - 1的取值位置,將會取到最大的一個擁有孩子節(jié)點的節(jié)點,并且該節(jié)點最多只有兩個孩子,并且其孩子節(jié)點是葉子節(jié)點,從該節(jié)點往前每個節(jié)點如果都能保證down的特性,則整個列表也就符合了堆的性質(zhì)了。

同樣,有down就有up,up保證的是某個節(jié)點如果向上沒有保證堆的性質(zhì),則將其與父節(jié)點進行交換,直到該節(jié)點放到某個特定位置保證了堆的性質(zhì)。代碼如下:

func up(h Interface, j int) {
	for {  // 死循環(huán)
		i := (j - 1) / 2 // parent  // j節(jié)點的父節(jié)點
		if i == j || !h.Less(j, i) {  // 如果越界,或者滿足堆的條件,則結(jié)束循環(huán)
			break
		}
		h.Swap(i, j)  // 否則將該節(jié)點和父節(jié)點交換
		j = i  // 對父節(jié)點繼續(xù)進行檢查直到根節(jié)點
	}
}

以上兩個方法就是最核心的方法了,所有暴露出來的方法無非就是對這兩個方法進行的封裝。我們來看看以下這些方法的源碼:

func Push(h Interface, x interface{}) {
	h.Push(x)  // 將新插入進來的節(jié)點放到最后
	up(h, h.Len()-1)  // 確保新插進來的節(jié)點網(wǎng)上能保證堆結(jié)構(gòu)
}

func Pop(h Interface) interface{} {
	n := h.Len() - 1  // 把最后一個節(jié)點和第一個節(jié)點進行交換,之后,從根節(jié)點開始重新保證堆結(jié)構(gòu),最后把最后那個節(jié)點數(shù)據(jù)丟出并返回
	h.Swap(0, n)
	down(h, 0, n)
	return h.Pop()
}

func Remove(h Interface, i int) interface{} {
	n := h.Len() - 1  pop只是remove的特殊情況,remove是把i位置的節(jié)點和最后一個節(jié)點進行交換,之后保證從i節(jié)點往下及往上都保證堆結(jié)構(gòu),最后把最后一個節(jié)點的數(shù)據(jù)丟出并返回
	if n != i {
		h.Swap(i, n)
		down(h, i, n)
		up(h, i)
	}
	return h.Pop()
}

func Fix(h Interface, i int) {
	if !down(h, i, h.Len()) {  // i節(jié)點的數(shù)值發(fā)生改變后,需要保證堆的再平衡,先調(diào)用down保證該節(jié)點下面的堆結(jié)構(gòu),如果有位置交換,則需要保證該節(jié)點往上的堆結(jié)構(gòu),否則就不需要往上保證堆結(jié)構(gòu),一個小小的優(yōu)化
		up(h, i)
	}
}

以上就是go里面的heap所有的源碼了,我也就不貼出完整版源碼了,以上理解全部基于個人的理解,如有不當(dāng)之處,還望批評指正。

4. 利用heap實現(xiàn)優(yōu)先級隊列

既然用到了heap,那就用heap實現(xiàn)一個優(yōu)先級隊列吧,這個功能是很好的一個功能。

源碼如下:

package main

import (
	"container/heap"
	"fmt"
)

type Item struct {
	value    string // 優(yōu)先級隊列中的數(shù)據(jù),可以是任意類型,這里使用string
	priority int    // 優(yōu)先級隊列中節(jié)點的優(yōu)先級
	index    int    // index是該節(jié)點在堆中的位置
}

// 優(yōu)先級隊列需要實現(xiàn)heap的interface
type PriorityQueue []*Item

// 綁定Len方法
func (pq PriorityQueue) Len() int {
	return len(pq)
}

// 綁定Less方法,這里用的是小于號,生成的是小根堆
func (pq PriorityQueue) Less(i, j int) bool {
	return pq[i].priority < pq[j].priority
}

// 綁定swap方法
func (pq PriorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].index, pq[j].index = i, j
}

// 綁定put方法,將index置為-1是為了標(biāo)識該數(shù)據(jù)已經(jīng)出了優(yōu)先級隊列了
func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]
	*pq = old[0 : n-1]
	item.index = -1
	return item
}

// 綁定push方法
func (pq *PriorityQueue) Push(x interface{}) {
	n := len(*pq)
	item := x.(*Item)
	item.index = n
	*pq = append(*pq, item)
}

// 更新修改了優(yōu)先級和值的item在優(yōu)先級隊列中的位置
func (pq *PriorityQueue) update(item *Item, value string, priority int) {
	item.value = value
	item.priority = priority
	heap.Fix(pq, item.index)
}

func main() {
	// 創(chuàng)建節(jié)點并設(shè)計他們的優(yōu)先級
	items := map[string]int{"二毛": 5, "張三": 3, "狗蛋": 9}
	i := 0
	pq := make(PriorityQueue, len(items)) // 創(chuàng)建優(yōu)先級隊列,并初始化
	for k, v := range items {             // 將節(jié)點放到優(yōu)先級隊列中
		pq[i] = &Item{
			value:    k,
			priority: v,
			index:    i}
		i++
	}
	heap.Init(&pq) // 初始化堆
	item := &Item{ // 創(chuàng)建一個item
		value:    "李四",
		priority: 1,
	}
	heap.Push(&pq, item)           // 入優(yōu)先級隊列
	pq.update(item, item.value, 6) // 更新item的優(yōu)先級
	for len(pq) > 0 {
		item := heap.Pop(&pq).(*Item)
		fmt.Printf("%.2d:%s index:%.2d\n", item.priority, item.value, item.index)
	}
}

輸出結(jié)果:

03:張三 index:-01
05:二毛 index:-01
06:李四 index:-01
09:狗蛋 index:-01

到此這篇關(guān)于Go語言利用heap實現(xiàn)優(yōu)先級隊列的文章就介紹到這了,更多相關(guān)Go語言 heap內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang開發(fā)動態(tài)庫的實現(xiàn)

    Golang開發(fā)動態(tài)庫的實現(xiàn)

    這篇文章主要介紹了Golang開發(fā)動態(tài)庫的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • go之如何設(shè)置GOROOT和GOPATH

    go之如何設(shè)置GOROOT和GOPATH

    這篇文章主要介紹了go之如何設(shè)置GOROOT和GOPATH問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • Go語言實現(xiàn)JSON解析的方法詳解

    Go語言實現(xiàn)JSON解析的方法詳解

    在日常項目中,使用Json格式進行數(shù)據(jù)封裝是比較常見的操作。本文將詳細(xì)講解如何利用Go語言實現(xiàn)JSON的解析,感興趣的小伙伴可以學(xué)習(xí)一下
    2022-04-04
  • Go高級特性探究之協(xié)程池詳解

    Go高級特性探究之協(xié)程池詳解

    在并發(fā)編程中,協(xié)程是?Go?語言的核心特性之一,本文將介紹如何使用?Go?協(xié)程池構(gòu)造一個協(xié)程池,并解決函數(shù)傳參問題、優(yōu)雅關(guān)閉協(xié)程池和保證協(xié)程安全的問題,感興趣的可以了解一下
    2023-06-06
  • golang 對私有函數(shù)進行單元測試的實例

    golang 對私有函數(shù)進行單元測試的實例

    這篇文章主要介紹了golang 對私有函數(shù)進行單元測試的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • Go語言sync.Cond使用方法詳解

    Go語言sync.Cond使用方法詳解

    Go語言標(biāo)準(zhǔn)庫中還包含條件變量 sync.Cond,它可以讓一組 Goroutine 都在滿足特定條件時被喚醒,每一個sync.Cond結(jié)構(gòu)體在初始化時都需要傳入一個互斥鎖,接下來我們將通過文中例子了解它的使用方法,感興趣的同學(xué)跟著小編一起來看看吧
    2023-07-07
  • golang?RPC包原理和使用詳細(xì)介紹

    golang?RPC包原理和使用詳細(xì)介紹

    golang的rpc支持三個級別的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是獨一無二的RPC,它和傳統(tǒng)的RPC系統(tǒng)不同,它只支持Go開發(fā)的服務(wù)器與客戶端之間的交互,因為在內(nèi)部,它們采用了Gob來編碼
    2022-09-09
  • golang中make和new的區(qū)別示例詳解

    golang中make和new的區(qū)別示例詳解

    Go 語言中的 new 和 make 一直是新手比較容易混淆的東西,咋一看很相似。不過解釋兩者之間的不同也非常容易,下面這篇文章主要介紹了golang中make和new的區(qū)別,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-08-08
  • golang?中?channel?的詳細(xì)使用、使用注意事項及死鎖問題解析

    golang?中?channel?的詳細(xì)使用、使用注意事項及死鎖問題解析

    這篇文章主要介紹了golang?中?channel?的詳細(xì)使用、使用注意事項及死鎖分析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-03-03
  • Go語言中更優(yōu)雅的錯誤處理

    Go語言中更優(yōu)雅的錯誤處理

    Go語言中的錯誤處理是一個被大家經(jīng)常拿出來討論的話題(另外一個是泛型)。篇文章我們將討論一下如何在現(xiàn)行的 Golang 框架下提供更友好和優(yōu)雅的錯誤處理。需要的朋友們可以參考借鑒,下面來一起看看吧。
    2017-02-02

最新評論