最小生成樹之普里姆(prim)演算法(C++實現)
最小生成樹之普里姆(Prim)演算法
最小生成樹:是在一個給定的無向圖G(V,E)中求一棵樹T,使得這棵樹擁有圖G中的所有頂點,且所有邊都是來自圖G中的邊,並且滿足整棵樹的邊權之和最小。
如上圖給出了一個圖G及其最小生成樹T,其中紅色的線即為最小生成樹的邊。最小生成樹T包含了圖G中所有的頂點,且由它們生成的樹的邊權之和為15,是所有生成樹中權值最小的。
最小生成樹有3個性質:
(1)最小生成樹是樹,因此其邊數等於定點數減1,且樹內一定不會有環;
(2)對給定的圖G(V,E),其最小生成樹可以不唯一,但是其邊權之和一定是唯一的;
(3)由於最小生成樹是無向圖上生成的,因此其根結點可以是這棵樹上的任意一個結點。
Prim演算法
Prim演算法是用來解決最小生成樹問題的。
基本思想:對圖G(V,E)設定集合S,存放已經被訪問的頂點,然後每次從集合V-S中選擇與集合S的最短距離最小的一個頂點(記為u),訪問並加入集合S。之後,令頂點u為中介點,優化所有從u能到達的頂點v與集合S之間的最短距離。這樣的操作執行n次(n為頂點個數),直到集合S已包含所有頂點。
注意:可以發現,prim演算法的思想和最短路徑中Dijkstra演算法的思想幾乎完全相同,只是在涉及最短距離時使用了集合S代替Dijkstra演算法中的起點s。
下面舉例來說明一下prim是如何求最小生成樹的。
假設從頂點V0開始,當前集合V={V0,V1,V2,V3,V4,V5}(藍色),集合S={}(黃色),頂點V0與集合S之間的距離為0,其它均為INF(一個很大的數):
(1)如圖(a),選擇與集合S距離最小的頂點V0,將其加入到集合S中,並連線頂點V0與其它頂點的邊,此時集合V={V1,V2,V3,V4,V5},集合S={V0};
(2)如圖(b),選擇與集合S距離最小的頂點V4,將其加入到集合S中,並連線頂點V4與其它頂點的邊,此時集合V={V1,V2,V3,V5},集合S={V0,V4};
(3)如圖(c),選擇與集合S距離最小的頂點V5,將其加入到集合S中,並連線頂點V5與其它頂點的邊,此時集合V={V1,V2,V3},集合S={V0,V4,V5};
(4)如圖(d),選擇與集合S距離最小的頂點V1,將其加入到集合S中,並連線頂點V1與其它頂點的邊,此時集合V={V2,V3},集合S={V0,V1,V4.V5};
(5)如圖(e),選擇與集合S距離最小的頂點V3,將其加入到集合S中,並連線頂點V3與其它頂點的邊,此時集合V={V2},集合S={V0,V1,V3,V4,V5};
(6)如圖(f),最後選擇頂點V2,將其加入到集合S中,此時集合V={},集合S={V0,V1,V2,V3,V4,V5};
此時集合S已經包含了所有的頂點,演算法結束。
Prim演算法流程:
對圖G(V,E)設定集合S,存放已經被訪問的頂點,然後執行n次下面的兩個步驟(n為頂點個數):
每次從集合V-S中選擇與集合S的最短距離最小的一個頂點(記為u),訪問並加入集合S,同時把這條離集合最近的邊加入到最小生成樹中。
令頂點u為中介點,優化所有從u能到達的未訪問的頂點v與集合S之間的最短距離。
具體實現:
prim演算法需要實現兩個關鍵的概念,即集合S的實現,頂點Vi(0<=i<=n-1)與集合S的最短距離。
集合S的實現使用一個bool型陣列vis[]表示頂點是否已經被訪問,其中vis[i]=true表示頂點Vi已被訪問,vis[i]=false則表示頂點Vi未被訪問;
令int型陣列d[]來存放頂點Vi與集合S的最短距離。初始時除了起點s的d[s]=0,其餘頂點賦值為一個很大的數來表示INF,即不可達。
程式碼:
main.cpp
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int INF = 1e9;
struct Node
{
int v;
int dis;
Node(int x, int y): v(x), dis(y) {}
};
int PrimG(int n, int s, vector<vector<int>> G, vector<bool> &vis, vector<int> &d)
{
/*
* n: 頂點個數
* s: 初始點
* G: 圖的鄰接矩陣
* vis: 標記頂點是否被訪問
* d: 儲存起點s到其它頂點的最短距離
* return: 最小生成樹的邊權之和
*/
fill(vis.begin(), vis.end(), false);
fill(d.begin(), d.end(), INF);
d[s] = 0;
int sum = 0;
for (int i = 0; i < n; ++i)
{
int u = -1;
int MIN = INF;
for (int j = 0; j < n; ++j)
{
if (!vis[j] && d[j] < MIN)
{
MIN = d[j];
u = j;
}
}
// 找不到小於INF的d[u],則剩下的頂點與集合S不連通
if (u == -1)
{
return -1;
}
vis[u] = true;
sum += d[u];
for (int v = 0; v < n; ++v)
{
if (!vis[v] && G[u][v] != INF && G[u][v] < d[v])
{
// 若將d[v]改為struct d {u, v, cost}, 則可以記錄MST的每一條邊
d[v] = G[u][v];
}
}
}
return sum;
}
int PrimAdj(int n, int s, vector<vector<Node>> Adj, vector<bool> &vis, vector<int> &d)
{
/*
* n: 頂點個數
* s: 初始點
* Adj: 圖的鄰接表
* vis: 標記頂點是否被訪問
* d: 儲存起點s到其它頂點的最短距離
* return: 最小生成樹的邊權之和
*/
fill(vis.begin(), vis.end(), false);
fill(d.begin(), d.end(), INF);
d[s] = 0;
int sum = 0;
for (int i = 0; i < n; ++i)
{
int u = -1;
int MIN = INF;
for (int j = 0; j < n; ++j)
{
if (!vis[j] && d[j] < MIN)
{
MIN = d[j];
u = j;
}
}
// 找不到小於INF的d[u],則剩下的頂點與集合S不連通
if (u == -1)
{
return -1;
}
vis[u] = true;
sum += d[u];
for (size_t j = 0; j < Adj[u].size(); ++j)
{
int v = Adj[u][j].v;
if (!vis[v] && Adj[u][j].dis < d[v])
{
// 若將d[v]改為struct d {u, v, cost}, 則可以記錄MST的每一條邊
d[v] = Adj[u][j].dis;
}
}
}
return sum;
}
int main()
{
int n = 6;
/*鄰接矩陣*/
vector<vector<int>> G = {{ 0, 4,INF,INF, 1, 2},
{ 4, 0, 6,INF,INF, 3},
{INF, 6, 0, 6,INF, 5},
{INF,INF, 6, 0, 4, 5},
{ 1,INF,INF, 4, 0, 3},
{ 2, 3, 5, 5, 3, 0}};
for (size_t i = 0; i < G.size(); ++i)
{
for (size_t j = 0; j < G[i].size(); ++j)
{
if (G[i][j] != INF && G[i][j] != 0)
{
cout << i << " " << j << " " << G[i][j] << endl;
}
}
}
cout << "--------" << endl;
/*鄰接表*/
vector<vector<Node>> Adj = {{Node(4,1),Node(5,2),Node(1,4)},
{Node(0,4),Node(5,3),Node(2,6)},
{Node(1,6),Node(3,6),Node(5,5)},
{Node(2,6),Node(4,4),Node(5,5)},
{Node(0,1),Node(5,3),Node(3,4)},
{Node(0,2),Node(1,3),Node(2,5),Node(3,5),Node(4,3)}};
for (size_t i = 0; i < Adj.size(); ++i)
{
for (size_t j = 0; j < Adj[i].size(); ++j)
{
cout << i << " " << Adj[i][j].v << " " << Adj[i][j].dis << endl;
}
}
cout << "--------" << endl;
vector<bool> vis(n);
vector<int> d(n);
int resG = PrimG(n, 0, G, vis, d); // 鄰接矩陣版
int resAdj = PrimAdj(n, 0, Adj, vis, d); // 鄰接表版
cout << resG << endl << resAdj << endl;
return 0;
}
注:上述程式碼只可以返回MST的cost值,若需要記錄MST的每一條邊,將d[v]修改為struct d {u, v, cost}即可。
執行結果:
參考部落格:https://blog.csdn.net/yf_li123/article/details/75148998