(1)概念
一個有 n 個結點的
(2)性質
-
一個連通圖可以有多個生成樹;
-
一個連通圖的所有生成樹都包含相同的頂點個數和邊數;
-
生成樹當中不存在環;
-
移除生成樹中的任意一條邊都會導致圖的不連通, 生成樹的邊最少特性;
-
在生成樹中添加一條邊會構成環。
-
對於包含n個頂點的連通圖,生成樹包含n個頂點和n-1條邊;
-
對於包含n個頂點的無向完全圖最多包含
顆生成樹。
(3)應用
例如:要在n個城市之間鋪設光纜,主要目標是要使這 n 個城市的任意兩個之間都可以通信,但鋪設光纜的費用很高,且各個城市之間鋪設光纜的費用不同,因此另一個目標是要使鋪設光纜的總費用最低。這就需要找到帶權的最小生成樹
MST算法之Prim
算法參考地址:
Prim算法的流程
1) 創建一組 mstSet,用於跟蹤 MST 中已包含的頂點。 2) 為輸入圖中的所有頂點分配一個鍵值。將所有鍵值初始化為 INFINITE。為第一個頂點分配鍵值為 0,以便首先選取它。 3) 雖然 mstSet 不包括所有頂點 ....a) 選擇一個在 mstSet 中不存在且具有最小鍵值的頂點 u。 ....b) 將 u 包含在 mstSet 中。 ....c) 更新 u 的所有相鄰頂點的鍵值。要更新鍵值,請循環訪問所有相鄰的頂點。對於每個相鄰的頂點 v,如果邊 u-v 的權重小於 v 的前一個鍵值,則將鍵值更新為 u-v 的權重使用鍵值的想法是從
讓我們通過以下示例來理解:
設置的 mstSet 最初是空的,分配給頂點的鍵是 {0, INF, INF, INF, INF, INF, INF, INF},其中 INF 表示無限。現在選取具有最小鍵值的頂點。選取頂點 0,將其包含在 mstSet 中。因此,mstSet 變得{0}。包含到 mstSet 后,更新相鄰頂點的鍵值。相鄰頂點 0 為 1 和 7。1 和 7 的鍵值將更新為 4 和 8。下圖顯示頂點及其鍵值,僅顯示具有有限鍵值的頂點。MST 中包含的頂點以綠色顯示。
選取具有最小鍵值且尚未包含在 MST 中(不在 mstSET 中)的頂點。選取頂點 1 並將其添加到 mstSet。所以 mstSet 現在變成 {0, 1}。更新相鄰頂點 1 的鍵值。頂點 2 的鍵值變為 8。
選取具有最小鍵值且尚未包含在 MST 中(不在 mstSET 中)的頂點。我們可以選擇頂點7或頂點2,讓頂點7被選中。所以 mstSet 現在變成 {0, 1, 7}。更新相鄰頂點 7 的鍵值。頂點 6 和 8 的鍵值變為有限(分別為 1 和 7)。
選取具有最小鍵值且尚未包含在 MST 中(不在 mstSET 中)的頂點。選取頂點 6。所以 mstSet 現在變成 {0, 1, 7, 6}。更新相鄰頂點 6 的鍵值。頂點 5 和 8 的鍵值將更新。
我們重復上述步驟,直到 mstSet 包含給定圖形的所有頂點。最后,我們得到下圖。
Prim算法的實現(golang)
prim算法的思想和Dijkstra很相似,在理解Dijkstra算法的前提下,理解Prim算法及其實現都會變得非常容易
//graph 中值為math.MaxInt的值為不可達
func prim(graph [][]int, randomVertex int) int {
n := len(graph)
//圖中已經遍歷到的頂點到未遍歷的頂點的最短的距離
dist := make([]int, n)
//圖中的頂點是否被訪問過
visit := make([]bool, n)
//最小生成書的路徑和
res := 0
curIdx := randomVertex
//標記初始訪問節點
visit[curIdx] = true
//初始化當前節點到未訪問節點的距離
for i := 0; i < n; i++ {
dist[i] = graph[curIdx][i]
}
//由於已經初始化一個節點,所以只需便利n-1次
for i := 1; i < n; i++ {
minor := math.MaxInt
for j := 0; j < n; j++ {
//尋找與已存在節點相接的最短距離的節點
if !visit[j] && dist[j] < minor {
minor = dist[j]
curIdx = j
}
}
//標記到最短距離的節點為已訪問
visit[curIdx] = true
//最短路徑值求和
res += minor
//重新初始化已訪問節點到未訪問節點的距離
for j := 0; j < n; j++ {
/**
僅更新沒有訪問過的節點且節點小於當前距離的節點
(因為如果graph[curIdx][j]> dist[j]的話,說明當前已經有節點到節點j的距離更小,
所以此邊(graph[curIdx][j])永遠也不會被用到)
*/
if !visit[j] && graph[curIdx][j] < dist[j] {
dist[j] = graph[curIdx][j]
}
}
}
return res
}
堆優化版的Prim算法
// Edge 最小生成樹prim算法(尋找已知節點到位置節點的最小路徑用堆優化)
//graph 中值為math.MaxInt的值為不可達
type Edge struct {
startVertex int
endVertex int
weight int
}
type EdgeHeap []Edge
func (h EdgeHeap) Len() int { return len(h) }
func (h EdgeHeap) Less(i, j int) bool { return h[i].weight < h[j].weight }
func (h EdgeHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *EdgeHeap) Push(x interface{}) {
*h = append(*h, x.(Edge))
}
func (h *EdgeHeap) Pop() interface{} {
n := len(*h)
res := (*h)[n-1]
*h = (*h)[:n-1]
return res
}
func primHeap(graph [][]int, randomVertex int) int {
//F代表兩點之間不可達
const F = math.MaxInt
n := len(graph)
//圖中已經遍歷到的頂點到未遍歷的頂點的最短的距離
distHeap := make(EdgeHeap, n)
//圖中的頂點是否被訪問過
visit := make([]bool, n)
//最小生成書的路徑和
res := 0
//節點訪問數
count := 1
curIdx := randomVertex
//標記初始訪問節點
visit[curIdx] = true
//初始化當前節點到未訪問節點的距離
for i := 0; i < n; i++ {
if graph[curIdx][i] != F {
distHeap[i] = Edge{curIdx, i, graph[curIdx][i]}
}
}
heap.Init(&distHeap)
for len(distHeap) > 0 && count < n {
edge := heap.Pop(&distHeap).(Edge)
//兩個頂點都已訪問過的話,說明如果在加入該條邊就構成環,所以跳過
if visit[edge.startVertex] && visit[edge.endVertex] {
continue
}
if !visit[edge.startVertex] {
visit[edge.startVertex] = true
count++
res += edge.weight
for i := 0; i < n; i++ {
if !visit[i] {
heap.Push(&distHeap, Edge{edge.startVertex, i,