最小生成樹兩種演算法比較與實現
阿新 • • 發佈:2019-02-11
Kruskal演算法 :(並查集)
時間複雜度O(elog2e),適合簡單圖。
演算法步驟:
1.構造一個有n個頂點的無邊子圖;
2.從原圖選擇邊權最小的邊加入該子圖,直至子圖成為一棵樹;
3.邊能加入子圖的條件是,邊的兩個端點u,v還未連通,Kruskal演算法中運用並查集的查詢來詢問兩個頂點是否連通;
Kruskal演算法的本質是,通過樹的合併(不斷加邊,構成子樹),來構建完整的生成樹。
就是先將邊的權按從小到大的順序排起來,依次選出邊,判斷兩點是否在一個集合中,如果在,看下一條邊,如果不在,則將兩點合併,這條邊為最小生成樹的一條邊。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 505;
int a[MAXN][MAXN];
typedef struct graph{
int u,v,cost;
friend bool operator <(const graph &a,const graph &b)
{
return a.cost<b.cost;
}
};
graph edge[MAXN*MAXN];
int b[MAXN];
int c[MAXN];
int find2(int x)
{
int i = x;
while(x != b[x])
{
x = b[x];
}
int p;
while(i != b[i])
{
p = b[i];
b[i] = x;
i = p;
}
return x;
}
void find1(int n,int m)
{
int p = find2(n);
int q = find2(m);
if(p!=q)
{
b[q]=b[p];
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n,i,j;
scanf("%d",&n);
for(i = 1;i <= n;++i)
{
b[i] = i;
}
for(i=1;i<=n;++i)
{
for(j=1;j<=n;++j)
{
scanf("%d",&a[i][j]);
}
}
int k=1;
for(i = 1;i <= n;++i)
{
for(j = i + 1;j <= n;++j)
{
edge[k].u = i;
edge[k].v = j;
edge[k].cost = a[i][j];
++k;
}
}
sort(edge+1,edge+k);
/*for(i = 1;i < k;++i)
{
printf("%d %d %d\n",edge[i].u,edge[i].v,edge[i].cost);
}*/
int ptr = 0;
for(i = 1;i < k;++i)
{
if(find2(edge[i].u)!=find2(edge[i].v))
{
find1(edge[i].u,edge[i].v);
c[ptr++] = i;
}
}
int mx = 0;
for(i = 0;i < ptr;++i)
{
mx = (mx < edge[c[i]].cost)?edge[c[i]].cost:mx;
}
printf("%d\n",mx);
}
return 0;
}
Prim演算法:(離散書上講的方法)
時間是複雜度O(n2),適合稠密圖。
Prim演算法的基本步驟:
1.初始化點集 V={x};
2.找到邊(u,v)滿足:u∈點集V,v不∈點集V;
3.選取2.中滿足條件的邊中最小的一條加入生成樹,並把v加入點集V,重複執行,直至原圖所有的點都加入點集V,就得到了一棵最小生成樹。
Tips:關於如何快速找到可以新增到生成樹的邊:
可以維護一個數組lowcost[i…j]記錄點集V到各個頂點的最小邊,即可快速找到邊,且每當有新的點加入點集V時,該陣列都要更新一次。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 2005;
const int inf = 0x3f3f3f3f;
int mp[MAXN][MAXN];
int lowcost[MAXN];
bool vis[MAXN];
int Prime(int n)
{
int i,j;
for(i = 1;i <= n;++i)
{
lowcost[i] = mp[1][i];
}
vis[1] = true;
lowcost[1] = 0;
int ans = 0;
for(i = 0;i < n - 1;++i)
{
int min = inf;
int k = 0;
for(j = 1;j <= n;++j)
{
if(!vis[j]&&min > lowcost[j])
{
min = lowcost[j];
k = j;
}
}
vis[k] = true;
ans = (ans > min)?ans:min;
for(j = 1;j <= n;++j)
{
if(!vis[j]&&lowcost[j] > mp[k][j])
lowcost[j] = mp[k][j];
}
}
return ans;
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
int i,j;
memset(mp,inf,sizeof(mp));
memset(vis,false,sizeof(vis));
for(i = 0;i < m;++i)
{
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
if(mp[x][y] > z)//防止出現重邊現象
mp[x][y] = z;
if(mp[y][x] > z)
mp[y][x] = z;
}
int ans = Prime(n);
printf("%d\n",ans);
}
return 0;
}