推薦:http://squirrelrao.iteye.com/blog/1044867
http://www.cnblogs.com/xwdreamer/archive/2011/06/16/2296997.html
http://blog.csdn.net/believejava/article/details/17414037
一、Prim算法:
Prim算法實現的是找出一個有權重連通圖中的最小生成樹,即:具有最小權重且連接到所有結點的樹。(強調的是樹,樹是沒有回路的)。
Prim算法是這樣來做的:
首先以一個結點作為最小生成樹的初始結點,然后以迭代的方式找出與最小生成樹中各結點權重最小邊,並加入到最小生成樹中。加入之后如果產生回路則跳過這條邊,選擇下一個結點。當所有結點都加入到最小生成樹中之后,就找出了連通圖中的最小生成樹了。
/*
prim 最小生成樹算法
過程:prim算法將圖分為兩部分,假設原頂點集為V,將其分為S和V-S兩部分,S為已經確定在最小生成樹上的頂點,
開始時,將任意一個頂點加入到S,然后每次在V-S中尋找距離S中的點最近的點。作為下一個加入最小生成樹上的點。
所有N個節點都加入到最小生成樹中時,最小生成樹構造完畢。
實現:對於鄰接矩陣構造的圖,可以用low[N]保存每個頂點到已加入生成樹中所有點的最小距離。
每次尋找這個距離最小的一個點加入最小生成樹中。再根據這個點的距離更新其它未加入生成樹中的點。
直到所有的點都加入到最小生成樹中。
*/
// Eg:HDU 1102
#include <stdio.h>
#include <string.h>
#include <iostream>
#define inf 1000000
using namespace std;
int g[210][210];
int low[210];
int vis[210]; // 表示該點是否已經加入最小生成樹中
int n;
int prim() {
for (int i=0; i<n; ++i) {
low[i] = g[0][i];
}
int ans = 0;
memset(vis, 0, sizeof(vis));
vis[0] = 1;
for (int i=1; i<n; ++i) { // 循環n-1次,找剩下的n-1個點。
int k = -1, mindis = inf;
for (int j=0; j<n; ++j) { // 循環找當前剩下的點中 距離最小生成樹點集距離最短的點。
if (!vis[j] && low[j] < mindis) {
mindis = low[j];
k = j;
}
}
if (k == -1) return -1;
vis[k] = 1; // 加入最小生成樹點集
ans += mindis;
for (int j=0; j<n; ++j) { // 更新沒加入最小生成樹的點中 距離是否會縮短。
/*if (!vis[j] && low[j] > low[k] + g[k][j]) {
low[j] = low[k] + g[k][j];
}*/
if (!vis[j] && low[j] > g[k][j]) { // 上面的if是錯的。low數組存儲的距離是當前點到生成樹中所有點距離最小的的點。
low[j] = g[k][j]; // 因為這個點加入最小生成樹集合中,可以和其中任意一個點連一條邊。
}
}
}
return ans;
}
int main() {
int q;
while(cin >> n) {
for (int i=0; i<n; ++i) {
for (int j=0; j<n; ++j) {
cin >> g[i][j];
}
}
cin >> q;
for (int i=0; i<q; ++i) {
int a, b;
cin >> a >> b;
a--, b--;
g[a][b] = 0;
g[b][a] = 0;
}
int ans = prim();
cout << ans << endl;
}
return 0;
}
二、Kruskal算法:
Kruskal算法與Prim算法的不同之處在於,Kruskal在找最小生成樹結點之前,需要對所有權重邊做從小到大排序。將排序好的權重邊依次加入到最小生成樹中,如果加入時產生回路就跳過這條邊,加入下一條邊。當所有結點都加入到最小生成樹中之后,就找出了最小生成樹。
無疑,Kruskal算法在效率上要比Prim算法快,因為Kruskal只需要對權重邊做一次排序,而Prim算法則需要做多次排序。盡管Prim算法每次做的算法涉及的權重邊不一定會涵蓋連通圖中的所有邊,但是隨着所使用的排序算法的效率的提高,Kruskal算法和Prim算法之間的差異將會清晰的顯性出來。
/*
最小生成樹 kruskal算法
過程:每次選取沒有參與構造最小生成樹並且加入之后不會構成回路的邊中權值最小的一條
作為最小生成樹的一條新邊。直至選擇了V-1條邊。
*/
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#define maxn 2100
using namespace std;
int fa[maxn], r[maxn];
void init(int n) {
for (int i=1; i<=n; ++i) {
fa[i] = i;
r[i] = 1;
}
}
int find_fa(int v) { // 遞歸式路徑壓縮
if (fa[v] != v) fa[v] = find_fa(fa[v]);
return fa[v];
}
/*int find_fa(int v) { // 非遞歸式路徑壓縮
int k, j, r;
r = v;
while(r != fa[r])
r = fa[r]; //找到根節點 記錄在r上。
k = v;
while(k != r) {
j = fa[k];
fa[k] = r;
k = j;
}
return r;
}*/
/*void unin(int u, int v) { // 非按秩合並
int fau = find_fa(u);
int fav = find_fa(v);
if (fau != fav)
fa[fav] = fau;
}*/
void unin(int u, int v) { // 按秩合並
int fau = find_fa(u);
int fav = find_fa(v);
if (fau == fav) return;
if (r[u] < r[v]) fa[fau] = fav;
else {
if (r[u] == r[v])
r[u]++;
fa[fav] = fau;
}
}
struct Edge {
int u, v, w;
}edge[1000010];
bool cmp(Edge a, Edge b) {
return a.w > b.w;
}
int ans;
int kruskal(int n, int m) { // 傳入頂點個數n 和 邊的個數m
init(n);
sort(edge, edge+m, cmp);
ans = 0;
int ret = 0; // 生成樹的總權值
int cnt = 0; // 已加入最小生成樹的邊的數量
for (int i=0; i<m; ++i) {
if (find_fa(1) == find_fa(n)) return -1;
int u = edge[i].u;
int v = edge[i].v;
if (find_fa(u) != find_fa(v)) {
cnt++;
ret += edge[i].w;
unin(u, v);
ans = edge[i].w;
}
if (cnt == n-1) return ret; // 已找到n-1條邊,生成樹構造完畢
}
return -1;
}
int main() {
int casee = 1;
int t;
cin >> t;
while(t--) {
int n, m;
cin >> n >> m;
for (int i=0; i<m; ++i) {
cin >> edge[i].u >> edge[i].v >> edge[i].w;
}
kruskal(n, m);
cout << "Scenario #" << casee++ << ":" << endl << ans << endl << endl;
}
return 0;
}
關於時間復雜度:
prim:該算法的時間復雜度為O(n2)。與圖中邊數無關,該算法適合於稠密圖。
kruskal:需要對圖的邊進行訪問,所以克魯斯卡爾算法的時間復雜度只和邊又關系,可以證明其時間復雜度為O(eloge)。適合稀疏圖。
