Prim和Kruskal演算法之C++實現
最近好長時間都困惑在這兩個演算法中,其實也不難,就是寫的時候比較費勁。現在總結一下。
首先說一下兩個演算法是幹嘛呢?
都是求解一個無向圖G的最小生成樹(minimum spanning tree),就是由該圖的那些連線G的所有頂點的邊構成的樹,其總值最低。這裡很重要一點就是要求G是連通的。
克魯斯卡爾(Kruskal)演算法因為只與邊相關,則適合求稀疏圖的最小生成樹。而prime演算法因為只與頂點有關,所以適合求稠密圖的最小生成樹。
克魯斯卡爾(Kruskal)演算法因為只與邊相關,則適合求稀疏圖的最小生成樹。而prime演算法因為只與頂點有關,所以適合求稠密圖的最小生成樹。
Prim
設圖G頂點集合為U,首先任意選擇圖G中的一點作為起始點a,將該點加入集合V,再從集合U-V中找到另一點b使得點b到V中任意一點的權值最小,此時將b點也加入集合V;以此類推,現在的集合V={a,b},再從集合U-V中找到另一點c使得點c到V中任意一點的權值最小,此時將c點加入集合V,直至所有頂點全部被加入V,此時就構建出了一顆MST。因為有N個頂點,所以該MST就有N-1條邊,每一次向集合V中加入一個點,就意味著找到一條MST的邊。
演算法要維持兩個陣列:
lowcost[i]:表示以i為終點的邊的最小權值,當lowcost[i]=0說明以i為終點的邊的最小權值=0,也就是表示i點加入了MST。
mst[i]: 表示對應lowcost[i]的起點,即說明邊(mst[i],i)是MST的一條邊,當mst[i]=0表示起點i加入MST。
具體過程詳:
程式碼
//最小生成樹的prim演算法
//http://blog.csdn.net/yeruby/article/details/38615045
#include<iostream>
#include<fstream>
using namespace std;
#define MAX 100
#define MAXCOST 0x7fffffff
int graph[MAX][MAX];
int prim(int graph[][MAX], int n,int begin)//begin作為初始頂點,n總的頂點數目
{
int lowcost[MAX];
int mst[MAX];
int sum = 0;
for (int i = 1;i <= n;i++) //初始化
{
if (i == begin)
continue;
lowcost[i] = graph[begin][i];//頂點1到其他定點的代價
mst[i] = begin;//初始都指向頂點begin
}
mst[begin] = 0;//頂點begin在MST集合裡
lowcost[begin] = 0;
for (int i = 1;i <= n;i++)
{
if (i == begin)
continue;
int min = MAXCOST;
int minid = 0;
for (int j = 1;j <= n;j++)//尋找lowcost中最小的邊
{
if (j == begin)
continue;
if (lowcost[j] < min&&lowcost[j] != 0)
{
min = lowcost[j];
minid = j;
}
}
lowcost[minid] = 0;
cout << "V" << mst[minid] << "-V" << minid << "=" << min << endl;//輸出找到的最小一條邊
sum = sum + min;
for (int j = 1;j <= n;j++)//更新其他定點(主要變化的就是以minid為起始的邊)
{
if (j == begin)
continue;
if (graph[minid][j] < lowcost[j])
{
lowcost[j] = graph[minid][j];
mst[j] = minid;
}
}
}
return sum;
}
int main()
{
ifstream in("input.txt");
int m, n;
int begin = 2;
in >> m >> n;
for (int i = 1;i <= m;i++)
{
for (int j = 1;j <= m;j++)
graph[i][j] = MAXCOST;
}
for (int k = 1;k <= n;k++)
{
int i, j, cost;
in >> i >> j >> cost;
graph[i][j] = cost;
graph[j][i] = cost;
}
int cost = prim(graph, m, begin);
cout << "最小權值和=" << cost << endl;
system("pause");
return 0;
}
Kruskal
演算法過程:
1.將圖各邊按照權值進行排序
2.將圖遍歷一次,找出權值最小的邊,(條件:此次找出的邊不能和已加入最小生成樹集合的邊構成環),若符合條件,則加入最小生成樹的集合中。不符合條件則繼續遍歷圖,尋找下一個最小權值的邊。
3.遞迴重複步驟1,直到找出n-1條邊為止(設圖有n個結點,則最小生成樹的邊數應為n-1條),演算法結束。得到的就是此圖的最小生成樹。
程式碼
//Kruskal 演算法
#include<iostream>
#include<algorithm>
#include<fstream>
using namespace std;
#define MAX 1000
int father[MAX], son[MAX];//father[i]表示i的祖先(根),son[i]表示i的兒子數目,如果沒有記為1,如果有一個記為2...
typedef struct Edge
{
int a;
int b;
int value;
};
bool cmp(Edge &a, Edge &b)
{
return a.value < b.value;
}
int unionsearch(int x)
{
return x == father[x] ? x : unionsearch(father[x]);
}
bool join(int x, int y)//合併x與y
{
int root1, root2;
root1 = unionsearch(x);//每個資料都有一個根(祖先)
root2 = unionsearch(y);
if (root1 == root2) //兩個的祖先一樣,說明已經屬於同一顆樹了,不用再合併了
return false;
else if (son[root1] >= son[root2]) //如果root1的兒子數目比較多,則把root2的祖先定為root1,同時root1的兒子數目更新
{
father[root2] = root1;
son[root1]= son[root1] + son[root2];
}
else
{
father[root1] = root2;
son[root2]= son[root1] + son[root2];
}
}
int main()
{
ifstream in("input.txt");
Edge edge[MAX];
int v, l;
bool flag=0;
int addedge_num=0, sum=0;
in >> v >> l;
for (int i = 1; i <= v; i++)
{
father[i] = i;
son[i] = 1; //初始時,每個頂點的兒子數為1,即本身自己
}
for (int i = 1; i <= l; i++)
{
in >> edge[i].a >> edge[i].b >> edge[i].value;
}
sort(edge + 1, edge + 1 + l, cmp);//按照升序排列
for (int i = 1; i < l; i++)
{
if (join(edge[i].a, edge[i].b))//說明能合併
{
addedge_num++;
sum = sum + edge[i].value;
cout<< edge[i].a << "->" << edge[i].b << endl;
}
if (addedge_num == v - 1)
{
flag = 1;
break;
}
}
if (flag)
{
cout << sum << endl;
}
else
{
cout << "Data error!" << endl;
}
system("pause");
return 0;
}
注意裡面涉及到並查集合的巧妙運用,仔細體會。