# 最小生成樹-Prim演算法介紹、證明、實作與複雜度分析
> 給定一連通帶權無向圖G(V,E),有n條邊,一個生成樹T就是一個有n-1條邊的連通子圖,求出邊權和最小的T。
## Prim演算法概述
Prim演算法步驟如下:
1. 選擇圖上任一頂點為起始點
2. 初始化:$V_{new}=\{x\}$,$x$為選定的起始點,$E_{new}=\{\}$
3. 重複以下步驟直到$V_{new}=V$:
1. 在E中選權重最小的邊$(u,v)$滿足$u \in V_{new} ,v \in V\backslash V_{new}$
2. 將$v$加入$V_{new}$,$(u,v)$加入$E_{new}$
4. $(V_{new},E_{new})$即為T
同樣地,我們必須證明演算法的正確性。
## 正確性證明
設Prim產生的生成樹為$T$,最小生成樹為$T^*$,根據定義,T的邊權和$\geq$$T^*$的邊權和。
設$T$的邊由小到大為:$e_{k_1},e_{k_2},...,e_{k_n}$,$T^*$的邊由小到大為:$e_{g_1},e_{g_2},...,e_{g_n}$,其中$n=\mid V \mid -1$
設第一個屬於$T$卻不屬於$T^*$的邊為$e_{d_1}$,兩端點為$(v_s,v_{e_1})$,同時也存在第一個屬於$T^*$卻不屬於$T$的邊$e_{d_2}$,兩端點為$(v_s,v_{e_2})$。
兩個邊起點相同,由Prim演算法性質可知,$w(e_{d_2})\geq w(e_{d_1})$此時將$e_{d_2}$換成$e_{d_1}$,可得新生成樹$T_{new}$,且$T^*$的邊權和$\geq$$T_{new}$的邊權和,又因為$T^*$是最小生成樹,$T^*$的邊權和$=$$T_{new}$的邊權和,以此類推,可得$T^*$的邊權和$=$$T$的邊權和,故得證$T$是最小生成樹。
## Prim演算法實作
Prim演算法的實作基本上即是模擬上面步驟,故不贅述。
程式碼如下:
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define Inf 2147483647
void Prim(vector<vector<int> > V){
vector<int> d; //記錄從U到V\U中各點的最短邊
vector<int> U; //記錄被加入到U中的點
vector<bool> visited; //記錄點是否被加入U中
vector<int> M; //記錄最終得到的各條邊
d.resize(V.size());
visited.resize(V.size());
fill(visited.begin(), visited.end(), false);
U.push_back(0);
visited[0] = true;
fill(d.begin(), d.end(), Inf);
d[0] = 0;
while (U.size() < V.size()){
int x = U[U.size() - 1];
for (int i = 0; i < d.size(); i++) {//更新從U到V-U中各點的最短邊
if (!visited[i] && V[x][i] < d[i]){
d[i] = V[x][i];
}
}
int min = Inf;
int index = 0;
for (int i = 0; i < d.size(); i++) { //尋找從U到V-U的最短邊
if (d[i] < min && !visited[i]){
min = d[i];
index = i;
}
}
if (index == 0){//不連通
return;
}
visited[index] = true;
U.push_back(index);
M.push_back(min);
}
for (int i = 0; i < M.size(); i++){ //輸出所得最小生成樹的各條邊
cout << M[i] << " ";
}
}
int main(){
int n = 0;//節點數
int m = 0;//邊數
cin >> n>>m;
vector<vector<int> > V;//圖(鄰接矩陣)
V.resize(n);
for (int i = 0; i < n; i++){
V[i].resize(n);
for (int j = 0; j < n; ++j){
V[i][j] = Inf;
}
}
for (int i = 0; i < m; i++){
int u, v, w;
cin >> u >> v >> w;
V[u][v] = w;
V[v][u] = w;
}
Prim(V);
return 0;
}
```
## 複雜度分析
對於每個點都要用$O(\mid V \mid)$時間來尋找與之相連的最短邊,一共要加入$\mid V \mid$個點進集合,故複雜度為$O(\mid V \mid^2)$,與Kruskal相比,在邊的密度大時Prim有更好的表現。
## 參考資料
https://zh.wikipedia.org/wiki/%E6%99%AE%E6%9E%97%E5%A7%86%E7%AE%97%E6%B3%95
https://iter01.com/536986.html
IOICAMP2021講義