最小生成树详解+经典例题

最小生成树:

1、定义

将给出的所有点连接起来(即从一个点可到任意一个点),且连接路径之和最小的图叫最小生成树。

2、算法:Kruskal 算法 和 Prime 算法;

3、这里给出Kruskal算法模板:

#include
#include
#include
#include
using namespace std;
struct node{
	int x,y;//树一条边的起点和终点;
	int w;// 边权(边的价值或大小)
}a[110];
int pre[110];//每个点的信息; 并查集所用;
bool cmp(struct node a,struct node b)
{
	return a.w<b.w;
}
int find(int root)// 查找函数(并查集)
{
	if(root!=pre[root]) root=find(pre[root]);
	return pre[root];
}
int main()
{
	int n,i,m;
	while(scanf("%d %d",&n,&m),n)
	{
		memset(pre,0,sizeof(pre));
		int sum=0,num=0;
		for(i=1;i<=m;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);//输入各个边的起点、终点和权值;
		sort(a+1,a+1+m,cmp);// 对各个边进行排序;
		for(i=1;i<=n;i++) pre[i]=i;
		for(i=1;i<=m;i++)// 从小到大开始枚举测试边;用并查集判断是否构成环; 不构成则加入;
		{
			int f1=find(a[i].x);
			int f2=find(a[i].y);
			if(f1!=f2)
			{
				pre[f1]=f2;
				sum+=a[i].w;//存储最小的边权和;
				num++;
			}
			if(num==n-1) break;// 满足树的性质; 边的条数==点的个数-1;
		}
		if(num==n-1)//判断是否存在;
		printf("%d\n",sum);
		else printf("?\n");
	}
	return 0;
}

4、总的来说 Kruskal 算法的步骤:

a、二维数组或结构体或者邻接表存储边权信息;
b、对边进行排序;
c、从小到大枚举测试每条边;
d、判断是否构成最小生成树;

5、经典例题:

1、畅通工程–HDU - 1863

传送门

分析:裸题-----直接套模板
代码:

#include
#include
#include
#include
using namespace std;
struct node{
	int x,y,w;
}a[110];
int pre[110];
int find(int root)
{
	if(root!=pre[root]) root=find(pre[root]);
	return pre[root];
}
bool cmp(node a,node b)
{
	return a.w<b.w;
}
int main()
{
	int n,m;
	while(scanf("%d%d",&m,&n),m)
	{
		int sum=0,num=0;
		memset(pre,0,sizeof(pre));
		for(int i=1;i<=n;i++) pre[i]=i;
		for(int i=1;i<=m;i++) 
		scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);
		sort(a+1,a+1+m,cmp);
		for(int i=1;i<=m;i++)
		{
			int f1=find(a[i].x);
			int f2=find(a[i].y);
			if(f1!=f2)
			{
				sum+=a[i].w;
				pre[f1]=f2;
				num++;
			}
		}
		if(num==n-1) printf("%d\n",sum);
		else printf("?\n");
	}
	return 0;
}

2、 Jungle Roads–POJ - 1251

传送门

分析:裸题----套模板,但是在数据存储时要严格按照题目要求来,有点麻烦,不直接;
代码:

#include
#include
#include
using namespace std;
struct node{
	int x,y,w;
}a[401];
int pre[401];
int find(int root)
{
	if(root!=pre[root]) pre[root]=find(pre[root]);
	return pre[root];
}
bool cmp(node a,node b)
{
	return a.w<b.w;
}
int main()
{
	int n;
	while(scanf("%d",&n),n)
	{
		int cnt=0;
		for(int i=0;i<n-1;i++)
		{
			char b; int num;
			cin>>b>>num;
			for(int j=1;j<=num;j++)
			{
				char c;int index;
				cin>>c>>index;
				a[cnt].x=b-'A'+1; a[cnt].y=c-'A'+1; a[cnt++].w=index;
			}
		}
		for(int i=1;i<=n;i++) pre[i]=i;
		sort(a,a+cnt,cmp);
		int sum=0;
		for(int i=0;i<cnt;i++)
		{
			int f1=find(a[i].x);
			int f2=find(a[i].y);
			if(f1!=f2)
			{
				sum+=a[i].w;
				pre[f1]=f2;
			}
		}
		printf("%d\n",sum);
	}
	return 0;
}

3、Networking—POJ - 1287

传送门

分析:裸题-----直接套模板

#include
#include
#include
#include
using namespace std;
struct node{
	int x,y;
	int w;
};
struct node a[5500];
int pre[5500];
bool cmp(struct node a,struct node b)
{
	return a.w<b.w;
}
int find(int root)
{
	if(root!=pre[root]) root=find(pre[root]);
	return pre[root];
}
int main()
{
	int n,i,m;
	while(scanf("%d",&n),n)
	{
		int sum=0,num=0;
		scanf("%d",&m);
		for(i=1;i<=m;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);
		sort(a+1,a+1+m,cmp);
		memset(pre,0,sizeof(pre));
		for(i=1;i<=n;i++) pre[i]=i;
		for(i=1;i<=m;i++)
		{
			int f1=find(a[i].x);
			int f2=find(a[i].y);
			if(f1!=f2)
			{
				pre[f1]=f2;
				sum+=a[i].w;
				num++;
			}
			if(num==m-1) break;
		}
		printf("%d\n",sum);
	}
	return 0;
}

4、洛谷–最小生成树模板;

传送门

分析:模板题;

#include
#include
#include
using namespace std;
typedef long long ll;
struct node{
	int x,y;
	ll w;
}a[200010];
int n,m,pre[5001];
int find(int root)
{
	if(root!=pre[root]) root=find(pre[root]);
	return pre[root];
}
bool cmp(node a,node b)
{
	return a.w<b.w;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) pre[i]=i;
	for(int i=1;i<=m;i++) scanf("%d%d%lld",&a[i].x,&a[i].y,&a[i].w);
	sort(a+1,a+m+1,cmp);
	ll sum=0;
	int num=0;
	for(int i=1;i<=m;i++)
	{
		int f1=find(a[i].x);
		int f2=find(a[i].y);
		if(f1!=f2)
		{
			pre[f1]=f2;
			sum+=a[i].w;
			num++;
		}
		if(num==n-1) break;
	}
	if(num!=n-1) printf("orz");
	else printf("%lld",sum);
	return 0;
}

5、Highways–POJ - 1751

传送门

题意:给出一些村庄,这些村庄直接已经建造了或没建造道路来两两相连,但道路并不全,不能使村庄相互连通,我们要做的是确定要再建造的路,并输出每条路的起点和终点;

分析:最小生成树,我们知道每个村庄的坐标(x,y);那么任意两个村庄的距离就可以得到了。我们可以算出每个村庄到其他村庄的距离并保存。运用Kruskal 算法将这些路排序枚举挑选即可。但要将我们已经建造的道路提前加入集合中。

代码:

#include
#include
#include
#include
using namespace std;
struct node{
	int x,y,w;
}a[507*507];// 村庄有n个,则道路最多有n*n条;将范围开到足够;
int pre[507];
int e[507][507];
int find(int root)
{
	if(root!=pre[root]) root=find(pre[root]);
	return pre[root];
}
bool cmp(struct node a, struct node b)
{
	return a.w<b.w;
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,ma=-1,num=0,i,j;
		scanf("%d",&n);
		for(i=1;i<=n;i++) pre[i]=i;
		for(i=1;i<=n;i++)
		{
			for(j=1;j<=n;j++)
			{
				int w;
				scanf("%d",&w);
				e[i][j]=w;
			}
		}
		int k=1;
		for(i=1;i<=n;i++)
		{
			for(j=1;j<i;j++)
			{
				a[k].x=i;
				a[k].y=j;
				a[k].w=e[i][j];
				k++;
			}
		}//将已经建造好的道路提前加入集合;
		sort(a+1,a+k+1,cmp);
		for(i=1;i<k;i++)
		{
			int f1=find(a[i].x);
			int f2=find(a[i].y);
			if(f1!=f2)
			{
				ma=max(ma,a[i].w);
				num++;
				pre[f1]=f2;
			}
			if(num==n-1) break;
		}
		printf("%d\n",ma);
	}
	return 0;	
}

6、Truck History–POJ - 1789

传送门

题意:给出n个长度为7的字符串,每个字符串代表一个车,定义车的距离是两个字符串间不同字母的个数,题目要求的数不同的车的距离的最小值,即所求的就是最小生成树。

分析:套prim模板。只是注意开始先求出距离(即相同位置的不同字母的个数)。
prime和Kruskal差不多,直接看代码就好;

#include
#include
#include
#include
using namespace std;
int e[2010][2010];
int dis[2010],book[2010],inf=0x3f3f3f3f;
char s[2010][11];
int main()
{
	int n;
	while(scanf("%d",&n),n)
	{
		for(int i=1;i<=n;i++)
		scanf("%s",s[i]);
		int cnt=0;
		for(int i=1;i<=n;i++)
		{
			for(int j=i+1;j<=n;j++)
			{
				int xx=0;
				for(int k=0;k<7;k++)
				if(s[i][k]!=s[j][k]) xx++;
				e[i][j]=e[j][i]=xx;
			}
		}
		memset(book,0,sizeof(book));
		int ans=0;
		for(int i=1;i<=n;i++) dis[i]=e[1][i];
		book[1]=1;
		for(int i=1;i<n;i++)
		{
			int mi=inf,u;
			for(int j=1;j<=n;j++) 
			{
				if(mi>dis[j]&&book[j]==0)
				{
					mi=dis[j];
					u=j;
				}
			} 
			book[u]=1;
			ans+=dis[u];
			for(int j=1;j<=n;j++) dis[j]=min(dis[j],e[u][j]);
		}
		printf("The highest possible quality is 1/%d.\n",ans);
	}
	return 0;
}

7、畅通工程再续 HDU - 1875

传送门

分析:与 5、Highways–POJ - 1751 类似;

#include
#include
#include
#include
using namespace std;
struct node{
	int x,y;
}a[1001];
struct mp{
	int x,y;
	double z;
}e[10010];
int pre[10010];
bool cmp(mp a,mp b)
{
	return a.z<b.z;
}
double jili(int i,int j)
{
	return (a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y)*1.0;
}
int find(int root)
{
	if(root!=pre[root]) root=find(pre[root]);
	return pre[root];
}
int n;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=0;i<=n;i++) pre[i]=i;
		for(int i=1;i<=n;i++)
		scanf("%d%d",&a[i].x,&a[i].y);
		int cnt=1;
		for(int i=1;i<=n;i++)
		{
			for(int j=i+1;j<=n;j++)
			{
				e[cnt].x=i;
				e[cnt].y=j;
				e[cnt++].z=sqrt(jili(i,j));
			}
		}
		cnt--;
		sort(e+1,e+cnt+1,cmp);
		double sum=0;
		int num=0;
		for(int i=1;i<=cnt;i++)
		{
			int f1=find(e[i].x);
			int f2=find(e[i].y);
			if(f1!=f2)
			{
				if(e[i].z>=10&&e[i].z<=1000)
				{
					num++;
					sum+=e[i].z;
					pre[f1]=f2;
				}
			}
			if(num==n-1) break;
		}
		if(num==n-1) printf("%.1f\n",sum*100);
		else printf("oh!\n");
	}
	return 0;
} 

总结:

最小生成树的题目大多比较固定,就是直接或者间接的套模板,但要注意数据的存储和应用。

你可能感兴趣的:(最小生成树详解+经典例题)