go語(yǔ)言編程學(xué)習(xí)實(shí)現(xiàn)圖的廣度與深度優(yōu)先搜索
圖的實(shí)現(xiàn)
所謂圖就是節(jié)點(diǎn)及其連接關(guān)系的集合。所以可以通過(guò)一個(gè)一維數(shù)組表示節(jié)點(diǎn),外加一個(gè)二維數(shù)組表示節(jié)點(diǎn)之間的關(guān)系。
//圖的矩陣實(shí)現(xiàn) typedef struct MGRAPH{ nodes int[]; //節(jié)點(diǎn) edges int[][]; //邊 }mGraph;
然而對(duì)于一些實(shí)際問(wèn)題,其鄰接矩陣中可能存在大量的0值,此時(shí)可以通過(guò)鄰接鏈表來(lái)表示稀疏圖,其數(shù)據(jù)結(jié)構(gòu)如圖所示
其左側(cè)為圖的示意圖,右側(cè)為圖的鄰接鏈表。紅字表示節(jié)點(diǎn)序號(hào),鏈表中為與這個(gè)節(jié)點(diǎn)相連的節(jié)點(diǎn),如1節(jié)點(diǎn)與2、5節(jié)點(diǎn)相連。由于在go
中,可以很方便地使用數(shù)組來(lái)代替鏈表,所以其鏈表結(jié)構(gòu)可以寫為
package main import "fmt" type Node struct{ value int; //節(jié)點(diǎn)為int型 }; type Graph struct{ nodes []*Node edges map[Node][]*Node //鄰接表示的無(wú)向圖 }
其中,map
為Go語(yǔ)言中的鍵值索引類型,其定義格式為map[<op1>]<op2>
,<op1>
為鍵,<op2>
為值。在圖結(jié)構(gòu)中,map[Node][]*Node
表示一個(gè)Node
對(duì)應(yīng)一個(gè)Node
指針?biāo)M成的數(shù)組。
下面將通過(guò)Go語(yǔ)言生成一個(gè)圖
//增加節(jié)點(diǎn) //可以理解為Graph的成員函數(shù) func (g *Graph) AddNode(n *Node) { g.nodes = append(g.nodes, n) } //增加邊 func (g *Graph) AddEdge(u, v *Node) { g.edges[*u] = append(g.edges[*u],v) //u->v邊 g.edges[*v] = append(g.edges[*v],u) //u->v邊 } //打印圖 func (g *Graph) Print(){ //range遍歷 g.nodes,返回索引和值 for _,iNode:=range g.nodes{ fmt.Printf("%v:",iNode.value) for _,next:=range g.edges[*iNode]{ fmt.Printf("%v->",next.value) } fmt.Printf("\n") } } func initGraph() Graph{ g := Graph{} for i:=1;i<=5;i++{ g.AddNode(&Node{i,false}) } //生成邊 A := [...]int{1,1,2,2,2,3,4} B := [...]int{2,5,3,4,5,4,5} g.edges = make(map[Node][]*Node)//初始化邊 for i:=0;i<7;i++{ g.AddEdge(g.nodes[A[i]-1], g.nodes[B[i]-1]) } return g } func main(){ g := initGraph() g.Print() }
其運(yùn)行結(jié)果為
PS E:\Code> go run .\goGraph.go 1:2->5-> 2:1->3->4->5-> 3:2->4-> 4:2->3->5-> 5:1->2->4->
BFS
廣度優(yōu)先搜索(BFS)是最簡(jiǎn)單的圖搜索算法,給定圖的源節(jié)點(diǎn)后,向外部進(jìn)行試探性地搜索。其特點(diǎn)是,通過(guò)與源節(jié)點(diǎn)的間隔來(lái)調(diào)控進(jìn)度,即只有當(dāng)距離源節(jié)點(diǎn)為 k k k的節(jié)點(diǎn)被搜索之后,才會(huì)繼續(xù)搜索,得到距離源節(jié)點(diǎn)為 k + 1 k+1 k+1的節(jié)點(diǎn)。
對(duì)于圖的搜索而言,可能存在重復(fù)的問(wèn)題,即如果1搜索到2,相應(yīng)地2又搜索到1,可能就會(huì)出現(xiàn)死循環(huán)。因此對(duì)于圖中的節(jié)點(diǎn),我們用searched
對(duì)其進(jìn)行標(biāo)記,當(dāng)其值為false
時(shí),說(shuō)明沒(méi)有被搜索過(guò),否則則說(shuō)明已經(jīng)搜索過(guò)了。
type Node struct{ value int; searched bool; } /*func initGraph() Graph{ g := Graph{} */ //相應(yīng)地更改節(jié)點(diǎn)生成函數(shù) for i:=1;i<=5;i++{ g.AddNode(&Node{i,false}) } /* ... */
此外,由于在搜索過(guò)程中會(huì)改變節(jié)點(diǎn)的屬性,所以map
所對(duì)應(yīng)哈希值也會(huì)發(fā)生變化,即Node
作為鍵值將無(wú)法對(duì)應(yīng)原有的鄰接節(jié)點(diǎn),所以Graph
中邊的鍵值更替為節(jié)點(diǎn)的指針,這樣即便節(jié)點(diǎn)的值發(fā)生變化,但其指針不會(huì)變化。
type Graph struct{ nodes []*Node edges map[*Node][]*Node //鄰接表示的無(wú)向圖 } //增加邊 func (g *Graph) AddEdge(u, v *Node) { g.edges[u] = append(g.edges[u],v) //u->v邊 g.edges[v] = append(g.edges[v],u) //u->v邊 } //打印圖 func (g *Graph) Print(){ //range遍歷 g.nodes,返回索引和值 for _,iNode:=range g.nodes{ fmt.Printf("%v:",iNode.value) for _,next:=range g.edges[iNode]{ fmt.Printf("%v->",next.value) } fmt.Printf("\n") } } func initGraph() Graph{ g := Graph{} for i:=1;i<=9;i++{ g.AddNode(&Node{i,false}) } //生成邊 A := [...]int{1,1,2,2,2,3,4,5,5,6,1} B := [...]int{2,5,3,4,5,4,5,6,7,8,9} g.edges = make(map[*Node][]*Node)//初始化邊 for i:=0;i<11;i++{ g.AddEdge(g.nodes[A[i]-1], g.nodes[B[i]-1]) } return g } func (g *Graph) BFS(n *Node){ var adNodes[] *Node //存儲(chǔ)待搜索節(jié)點(diǎn) n.searched = true fmt.Printf("%d:",n.value) for _,iNode:=range g.edges[n]{ if !iNode.searched { adNodes = append(adNodes,iNode) iNode.searched=true fmt.Printf("%v ",iNode.value) } } fmt.Printf("\n") for _,iNode:=range adNodes{ g.BFS(iNode) } } func main(){ g := initGraph() g.Print() g.BFS(g.nodes[0]) }
該圖為
輸出結(jié)果為
PS E:\Code\goStudy> go run .\goGraph.go 1:2->5->9-> 2:1->3->4->5-> 3:2->4-> 4:2->3->5-> 5:1->2->4->6->7-> 6:5->8-> 7:5-> 8:6-> 9:1-> //下面為BFS結(jié)果 1:2 5 9 2:3 4 3: 4: 5:6 7 6:8 8: 7: 9:
DFS
深度優(yōu)先遍歷(DFS)與BFS的區(qū)別在于,后者的搜索過(guò)程可以理解為逐層的,即可將我們初始搜索的節(jié)點(diǎn)看成父節(jié)點(diǎn),那么與該節(jié)點(diǎn)相連接的便是一代節(jié)點(diǎn),搜索完一代節(jié)點(diǎn)再搜索二代節(jié)點(diǎn)。DFS則是從父節(jié)點(diǎn)搜索開(kāi)始,一直搜索到末代節(jié)點(diǎn),從而得到一個(gè)末代節(jié)點(diǎn)的一條世系;然后再對(duì)所有節(jié)點(diǎn)進(jìn)行遍歷,找到另一條世系,直至不存在未搜索過(guò)的節(jié)點(diǎn)。
其基本步驟為:
- 首先選定一個(gè)未被訪問(wèn)過(guò)的頂點(diǎn) V 0 V_0 V0作為初始頂點(diǎn),并將其標(biāo)記為已訪問(wèn)
- 然后搜索 V 0 V_0 V0鄰接的所有頂點(diǎn),判斷是否被訪問(wèn)過(guò),如果有未被訪問(wèn)的頂點(diǎn),則任選一個(gè)頂點(diǎn) V 1 V_1 V1進(jìn)行訪問(wèn),依次類推,直到 V n V_n Vn不存在未被訪問(wèn)過(guò)的節(jié)點(diǎn)為止。
- 若此時(shí)圖中仍舊有頂點(diǎn)未被訪問(wèn),則再選取其中一個(gè)頂點(diǎn)進(jìn)行訪問(wèn),否則遍歷結(jié)束。
我們先實(shí)現(xiàn)第二步,即單個(gè)節(jié)點(diǎn)的最深搜索結(jié)果
func (g *Graph) visitNode(n *Node){ for _,iNode:= range g.edges[n]{ if !iNode.searched{ iNode.searched = true fmt.Printf("%v->",iNode.value) g.visitNode(iNode) return } } } func main(){ g := initGraph() g.nodes[0].searched = true fmt.Printf("%v->",g.nodes[0].value) g.visitNode(g.nodes[0]) }
結(jié)果為
PS E:\Code> go run .\goGraph.go 1->2->3->4->5->6->8->
即
可見(jiàn),還有節(jié)點(diǎn)7、9未被訪問(wèn)。
完整的DFS算法只需在單點(diǎn)遍歷之前,加上一個(gè)對(duì)所有節(jié)點(diǎn)的遍歷即可
func (g *Graph) DFS(){ for _,iNode:=range g.nodes{ if !iNode.searched{ iNode.searched = true fmt.Printf("%v->",iNode.value) g.visitNode(iNode) fmt.Printf("\n") g.DFS() } } } func main(){ g := initGraph() g.nodes[0].searched = true fmt.Printf("%v->",g.nodes[0].value) g.visitNode(g.nodes[0]) }
結(jié)果為
PS E:\Code> go run .\goGraph.go 1->2->3->4->5->6->8-> 7-> 9->
以上就是go語(yǔ)言編程學(xué)習(xí)實(shí)現(xiàn)圖的廣度與深度優(yōu)先搜索的詳細(xì)內(nèi)容,更多關(guān)于go語(yǔ)言實(shí)現(xiàn)圖的廣度與深度優(yōu)先搜索的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解golang中的結(jié)構(gòu)體編解碼神器Mapstructure庫(kù)
mapstructure是GO字典(map[string]interface{})和Go結(jié)構(gòu)體之間轉(zhuǎn)換的編解碼工具,這篇文章主要為大家介紹一下Mapstructure庫(kù)的相關(guān)使用,希望對(duì)大家有所幫助2023-09-09Go語(yǔ)言實(shí)現(xiàn)的排列組合問(wèn)題實(shí)例(n個(gè)數(shù)中取m個(gè))
這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)的排列組合問(wèn)題,結(jié)合實(shí)例形式分析了Go語(yǔ)言實(shí)現(xiàn)排列組合數(shù)學(xué)運(yùn)算的原理與具體操作技巧,需要的朋友可以參考下2017-02-02幾個(gè)小技巧幫你實(shí)現(xiàn)Golang永久阻塞
Go 的運(yùn)行時(shí)的當(dāng)前設(shè)計(jì),假定程序員自己負(fù)責(zé)檢測(cè)何時(shí)終止一個(gè) goroutine 以及何時(shí)終止該程序。有時(shí)候我們需要的是使程序阻塞在這一行,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2021-12-12go 原生http web 服務(wù)跨域restful api的寫法介紹
這篇文章主要介紹了go 原生http web 服務(wù)跨域restful api的寫法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Go語(yǔ)言使用buffer讀取文件的實(shí)現(xiàn)示例
本文主要介紹了Go語(yǔ)言使用buffer讀取文件的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04