最小代价生成树有两种方法,prim 和 Kruskal. Prim跟迪杰斯特拉算法有点相似,Kruskal又用到了并查集的知识。
近几天,在看了最短路径之后,又看了Prim算法的最小代价生成树,感觉十分相似,可能有些混淆。这里,梳理一下,大佬们看看可对?
先放上这道最小代价生成树问题的代码:
//AC
//HDU 1102 Constructing Roads
#include<iostream>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
int dis[105][105];
int vis[105];
int ans[105];
const int maxn = 0x3f3f3f3f;
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
memset(dis,maxn,sizeof(dis));
memset(ans,maxn,sizeof(ans));
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>dis[i][j];
}
}
int q;
cin>>q;
for(int i=1;i<=q;i++)
{
int a,b;
cin>>a>>b;
dis[a][b]=0;
dis[b][a]=0;
}
//核心的prim算法
ans[1]=0;// start from 1
for(int i=1;i<=n;i++)
{
int tempdis=maxn;
int tempi=0;
for(int j=1;j<=n;j++)
{
if(vis[j]==0 && tempdis>ans[j])
{
tempdis=ans[j];
tempi=j;
}
}
vis[tempi]=1;
for(int j=1;j<=n;j++)
{
if(vis[j]==0 && ans[j]>dis[tempi][j])
{
ans[j]=dis[tempi][j];
}
}
}
int res=0;
for(int i=1;i<=n;i++)
res+=ans[i];
cout<<res<<endl;
}
return 0;
}
哦对了,这题开始WA了一发,因为题目没说输入是多组的,这个WA点也太坑了。窒息。。。
注意到Prim算法中:
- 每次循环中更新的数组ans[ ] 记录的是,当前的tempi到第i个点的距离。(对比迪杰斯特拉算法中的ans[ ])
- 外层循环一共有n次,因为包括我打算开始的“源点”。
- “源点”开始时,不能标记,因为外层的第一次循环里面要使用到“源点“”,而且初始化ans[源点]=0;
- 在内层的第二个for循环中,判断的是ans[j]>dis[tempi][j]. 也可以称作“松弛”?
同时呢,我找到了单源最短路径,迪杰斯特拉算法的一道题。
重新码了一遍代码,要不是再看自己原来写的,,竟然WA了3发,功力不够。
//AC
//HDU 3790 twice
#include<iostream>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
int n,m;
int dis[1005][1005];
int cost[1005][1005];
int vis[1005];
int ans[1005];
int expense[1005];
const int maxn=0x3f3f3f3f;
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
if(m==0 && n==0) return 0;
for(int i=0;i<=n;i++)
{
for(int j=0;j<=n;j++)
{
if(i==j)
{
cost[i][i]=0;
dis[i][i]=0;
}
else
{
cost[i][j]=maxn;
dis[i][j]=maxn;
}
}
}
for(int i=0;i<m;i++)
{
int a,b,d,p;
scanf("%d%d%d%d",&a,&b,&d,&p);
if(dis[a][b]>d)
{
dis[a][b]=d;
cost[a][b]=p;
dis[b][a]=d;
cost[b][a]=p;
}
}
int s,t;
cin>>s>>t;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
ans[i]=dis[s][i];
expense[i]=cost[s][i];
}
vis[s]=1;
for(int i=1;i<=n-1;i++)
{
int tempdis=maxn;
int tempi=0;
for(int j=1;j<=n;j++)
{
if(vis[j]==0 && tempdis>ans[j])
{
tempdis=ans[j];
tempi=j;
}
}
//if(tempi==0) break;
vis[tempi]=1;
// cout<<"tempi:"<<tempi<<" tempdis:"<<tempdis<<endl;
for(int j=1;j<=n;j++)
{
if(vis[j]==0 && ans[j]>ans[tempi]+dis[tempi][j])
{
ans[j]=ans[tempi]+dis[tempi][j];
expense[j]=expense[tempi]+cost[tempi][j];
}
else if(vis[j]==0 && ans[j]==ans[tempi]+dis[tempi][j])
{
expense[j]=expense[tempi]+cost[tempi][j];
}
}
if(tempi==t)
break;
}
cout<<ans[t]<<" "<<expense[t]<<endl;
}
return 0;
}
注意到迪杰斯特拉算法中:
- 初始化时,ans[ ] 数组的含义是“源点”距离第 i 个元素的距离。
- 初始化是,源点是已经标记了的。vis[源点]=1;
- 外层循环最大可能是 n-1 次,因为源点已经标记过了。
- 内层循环,更新时 ans[j] > ans[tempi] + len[tempi][j] , 称为“松弛”。
姑且谈这么多吧。
下面给出Kruskal的算法:
//HDU 1102 Kruskal
#include<iostream>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
const int maxn = 10005;
int dis[105][105];
int pre[105];
int cnt=0;
int ans=0;
int n;
struct Edge
{
int u;
int v;
int w;
set(int a,int b,int c)
{
u=a;v=b;w=c;
}
}edge[maxn];
bool cmp(Edge a,Edge b)
{
return a.w<b.w;
}
int find(int x)
{
int r=x;
while(r!=pre[r])
{
r=pre[r];
}
int j=x;
while(j!=r)
{
int t=pre[j];
pre[j]=r;
j=t;
}
return r;
}
int Kruskal()
{
for(int i=0;i<105;i++)
pre[i]=i;
sort(edge,edge+cnt-1,cmp);
int countedge=0;
for(int i=0;i<cnt;i++)
{
int f=find(edge[i].u);
int e=find(edge[i].v);
if(f!=e)
{
ans+=edge[i].w;
pre[f]=e;
countedge++;
if(countedge==n-1)
break;
}
}
return ans;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
cnt=0;ans=0;
memset(dis,0,sizeof(dis));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
int t;
cin>>t;
dis[i][j]=t;
}
}
int q;
cin>>q;
for(int i=1;i<=q;i++)
{
int a,b;
cin>>a>>b;
dis[a][b]=0;
dis[b][a]=0;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
edge[cnt++].set(i,j,dis[i][j]);
}
}
int res=Kruskal();
cout<<res<<endl;
}
return 0;
}
并查集的初始化,总是易忘。
还有路径压缩时的while循环 ,每次都会大脑短路一下。
循环的终止条件是,已经标记了 n-1条边哦。