HDU 1102 一道最小代价生成树问题。(比较迪杰斯特拉算法,同时给出Kruskal算法)

14 篇文章 0 订阅

最小代价生成树有两种方法,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算法中:

  1. 每次循环中更新的数组ans[ ] 记录的是,当前的tempi到第i个点的距离。(对比迪杰斯特拉算法中的ans[ ])
  2. 外层循环一共有n次,因为包括我打算开始的“源点”。
  3. “源点”开始时,不能标记,因为外层的第一次循环里面要使用到“源点“”,而且初始化ans[源点]=0;
  4. 在内层的第二个for循环中,判断的是ans[j]>dis[tempi][j]. 也可以称作“松弛”?

 

 

同时呢,我找到了单源最短路径,迪杰斯特拉算法的一道题。

HDU 3790

重新码了一遍代码,要不是再看自己原来写的,,竟然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;
	
}

注意到迪杰斯特拉算法中:

  1. 初始化时,ans[ ] 数组的含义是“源点”距离第 i 个元素的距离。
  2. 初始化是,源点是已经标记了的。vis[源点]=1;
  3. 外层循环最大可能是 n-1 次,因为源点已经标记过了。
  4. 内层循环,更新时 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条边哦。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值