🎯 什麼情境該想到我

當你「要最小生成樹,而且圖是稠密的」的時候。
★ 它的程式碼和 Dijkstra 最短路徑 幾乎一模一樣——差別只有一行。理解那一行的差異,就同時理解了兩個演算法。

⚙️ 怎麼用(機制與對照)

Prim:從一個點開始,把樹長出去

1. 任選一個起點,樹 T = {起點}
2. 重複 V-1 次:
     ★ 在「一端在 T 內、另一端在 T 外」的所有邊中,挑權重最小的
     把那條邊和它的外側端點加入 T

和 Kruskal 最小生成樹 的差別:

Kruskal 的中間狀態是一片森林(很多棵小樹同時長),最後合併成一棵。
Prim 的中間狀態始終是一棵連通的樹,只是越長越大。

★ 正確性:同樣是割性質

取割 (已在樹裡的點)、(樹外的點)。
Prim 每次挑的正是跨越這個割的最小邊 → 由割性質(證明見 Kruskal 最小生成樹),加它一定安全。

★ Kruskal 和 Prim 是「同一個定理的兩種用法」:
都靠割性質,只是選擇割的方式不同——Kruskal 用「連通分量」當割,Prim 用「已建好的樹」當割。

實作一: 陣列版(★ 稠密圖首選)

// g 是鄰接矩陣,g[i][j] = 邊權(無邊為 INF)
func PrimDense(g [][]int, n int) int {
    const INF = math.MaxInt / 2
    minE := make([]int, n)          // minE[v] = v 連到樹的最小邊權
    inTree := make([]bool, n)
    for i := range minE { minE[i] = INF }
    minE[0] = 0
    total := 0
 
    for i := 0; i < n; i++ {
        // ★ 線性找出樹外距離最小的點 —— O(V)
        u := -1
        for v := 0; v < n; v++ {
            if !inTree[v] && (u == -1 || minE[v] < minE[u]) { u = v }
        }
        if minE[u] == INF { return -1 }     // 圖不連通
        inTree[u] = true
        total += minE[u]
        // ★ 用 u 更新所有樹外點的 minE —— O(V)
        for v := 0; v < n; v++ {
            if !inTree[v] && g[u][v] < minE[v] { minE[v] = g[u][v] }
        }
    }
    return total
}

★ 稠密圖上這個版本反而最快:,沒有堆、沒有配置、連續記憶體存取。
時,堆版的 反而更慢。

實作二:堆優化 (稀疏圖)

把「線性找最小」換成 二元堆積,並用惰性刪除(同 Dijkstra 最短路徑)。

★ Prim vs Dijkstra:只差一行

// Dijkstra:到起點的「累積距離」
if dist[u] + w < dist[v] { dist[v] = dist[u] + w }
//         ^^^^^^^^^^^^ 累加
 
// Prim:到「樹」的單邊距離
if w < minE[v] { minE[v] = w }
//  ^ 不累加
DijkstraPrim
dist/minE[v] 的意義從起點到 的最短路徑長從 連到樹的最小單邊權
鬆弛dist[u] + w(累加)w(不累加)
求什麼最短路徑最小生成樹
負權❌ 會錯✅ 沒問題

★ 「Prim 不受負權影響」的原因:它從不累加路徑長度,只比較單一邊權。
而 Dijkstra 的正確性建立在「路徑只會變長」上(見該頁的反例)。
這一行差異解釋了兩者所有的性質差異。

複雜度

實作複雜度適合
陣列★ 稠密圖()
二元堆稀疏圖
Fibonacci heap理論最優,實務不用

★ 怎麼選(與 Kruskal 對照)

情況選
稠密圖()Prim 陣列版 —— 連堆都不用
稀疏圖()Kruskal 最小生成樹
邊已經排好序Kruskal(近乎線性)
只是隨手寫一個Kruskal(好寫)
圖以鄰接矩陣給定Prim 陣列版(天然契合)
圖以邊清單給定Kruskal(天然契合)

★ 一個實用的記法:「圖怎麼給的,就用對應的演算法。」
鄰接矩陣 → Prim;邊清單 → Kruskal。

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • ★ 稠密圖不要用堆版。 比 慢,而且多了堆的配置與快取失誤。
  • 只適用無向圖(同 Kruskal)。
  • 圖不連通時 Prim 只會長出起點所在的那一棵——要偵測 minE[u] == INF 並回報。
    (Kruskal 天然會產生「最小生成森林」,Prim 不會。)
  • INF 溢位:陣列版沒有累加所以較安全,但初始化仍要小心。
  • 要記錄 MST 的邊(不只總權重)時,要額外維護 parent[v]。
  • 負權沒問題(見上)。
  • 邊權相同時 MST 不唯一,但總權重唯一。

🔗 相關工具