1. 程式人生 > 其它 >最小生成樹MST演算法(Prim、Kruskal)

最小生成樹MST演算法(Prim、Kruskal)

最小生成樹MST(Minimum Spanning Tree)

(1)概念

一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊,所謂一個 帶權圖 的最小生成樹,就是原圖中邊的權值最小的生成樹 ,所謂最小是指邊的權值之和小於或者等於其它生成樹的邊的權值之和。

(2)性質

  • 一個連通圖可以有多個生成樹;

  • 一個連通圖的所有生成樹都包含相同的頂點個數和邊數;

  • 生成樹當中不存在環;

  • 移除生成樹中的任意一條邊都會導致圖的不連通, 生成樹的邊最少特性;

  • 在生成樹中新增一條邊會構成環。

  • 對於包含n個頂點的連通圖,生成樹包含n個頂點和n-1條邊;

  • 對於包含n個頂點的無向完全圖最多包含  顆生成樹。

(3)應用

例如:要在n個城市之間鋪設光纜,主要目標是要使這 n 個城市的任意兩個之間都可以通訊,但鋪設光纜的費用很高,且各個城市之間鋪設光纜的費用不同,因此另一個目標是要使鋪設光纜的總費用最低。這就需要找到帶權的最小生成樹

MST演算法之Prim

演算法參考地址:Prim的最小生成樹(MST)|貪婪的演算法-5 - 極客 (geeksforgeeks.org)

 

Prim演算法的流程

1) 建立一組 mstSet,用於跟蹤 MST 中已包含的頂點。 2) 為輸入圖中的所有頂點分配一個鍵值。將所有鍵值初始化為 INFINITE。為第一個頂點分配鍵值為 0,以便首先選取它。 3)

 雖然 mstSet 不包括所有頂點 ....a) 選擇一個在 mstSet 中不存在且具有最小鍵值的頂點 u ....b) 將 u 包含在 mstSet 中。 ....c) 更新 u 的所有相鄰頂點的鍵值。要更新鍵值,請迴圈訪問所有相鄰的頂點。對於每個相鄰的頂點 v,如果邊 u-v 的權重小於 v 的前一個鍵值,則將鍵值更新為 u-v 的權重使用鍵值的想法是從剪下中選取最小權重邊。鍵值僅用於尚未包含在 MST 中的折點,這些折點的鍵值表示將它們連線到 MST 中包含的折點集的最小權重邊。

讓我們通過以下示例來理解:

設定的 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