【kruskal算法】 苗条的生成树

本文介绍了一种基于Kruskal算法求解最小苗条度生成树的问题,通过调整最小生成树的构造方式来寻找不同生成树,最终确定最苗条的生成树。文章详细解析了算法思路,并提供了实现代码。

问题 N: 【基础图论】苗条的生成树

时间限制: 1 Sec   内存限制: 64 MB
提交: 5   解决: 1
[ 提交][ 状态][ 讨论版]

题目描述

给定一个带权无向图G,你可以按如下方法定义一棵生成树: 图 G 是一个顶点与边的集合 (V, E), V 表示顶点集合 {v1, v2, …, vn} , E 表示无向的边的集合 {e1, e2, …, em}. 每条边 e ∈ E 有一个权值 w(e). 生成树 T 是一棵树(一个连通的无环子图) ,它用 n - 1 条边连接了全部的 n 个顶点。 生成树T的苗条度定义为生成树的n - 1条边中权值最大的边和权值最小的边的权值之差。 例如,图5(a)有4个顶点 {v1, v2, v3, v4} 和5条边 {e1, e2, e3, e4, e5}. 各条边的权值分别为 w(e1) = 3, w(e2) = 5, w(e3) = 6, w(e4) = 6, w(e5) = 7 ,如图5(b)所示. 该图存在多棵生成树,图6(a)~(d)展示了其中的4棵。生成树 Ta 的3条边的权值分别是 3, 6 和 7. 权值最大为 7 最小为 3,因此 Ta 的苗条度为 4。 相应的,生成树 Tb, Tc 和 Td 的苗条度分别是3 , 2 和 1。 显而易见,任意一棵生成树的苗条度都大于或等于1,因此Td是所有生成树中最苗条的。 你的任务就是写一个程序来计算出最小的苗条度。

输入

输入文件包含多组测试数据,每组测试数据格式如下: 第1行:2个空格分开的整数n m (2 ≤ n ≤ 100 and 0 ≤ m ≤ n(n − 1)/2),n表示顶点数,m表示边数。 接下来m行,每行3个空格分开的整数a b w(1 ≤ w ≤ 10000) , 表示顶点a与顶点b之间有一条边,权值为w。输入数据保证图G是一条简单图,也就是说,不存在自环,两条顶点之间仅有一条边。 最后一组测试数据后的2个空格分开的0表示输入结束。

备注:同样题目数据加强版:OJ2586.

输出

对每组测试数据,如果图存在生成树,输出生成树的苗条度最小的值;否则,输出-1。

样例输入

4 5
1 2 3
1 3 5
1 4 6
2 4 6
3 4 7
4 6
1 2 10
1 3 100
1 4 90
2 3 20
2 4 80
3 4 40
2 1
1 2 1
3 0
3 1
1 2 1
3 3
1 2 2
2 3 5
1 3 6
5 10
1 2 110
1 3 120
1 4 130
1 5 120
2 3 110
2 4 120
2 5 130
3 4 120
3 5 110
4 5 120
5 10
1 2 9384
1 3 887
1 4 2778
1 5 6916
2 3 7794
2 4 8336
2 5 5387
3 4 493
3 5 6650
4 5 1422
5 8
1 2 1
2 3 100
3 4 100
4 5 100
1 5 50
2 5 50
3 5 50
4 1 150
0 0

样例输出

1
20
0
-1
-1
1
0
1686
50


分析:
           要解这道题必须要了解kruskal算法,如何生成不同的最小生成树?可以发现,在将边从小到大排序后,必然有一条边是最大的且这条边是生成树中必需的一条边,意思就是,排序后,最小生成树的苗条度必定不超过最大边减去最小边。
            
        在生成树过后,1~m条边中会有一条生成树中最大的边,因为最小生成树的性质,如果我们把边从后往前删,对结果是没有影响的,所以需要将每次生成树开始查找的起始边从小到大循环,这样,生成树的最小边就可能会改变,就能生成不同的树
            
           还有,当总边数小于n-1时,就不可能有树,这是循环的终止条件


代码如下:

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,s;
struct node
{
	int l,r,o;
	bool operator < (const node &p)const {return o<p.o;}
}a[5100];//空间记得开够
int f[110];
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
void kus(int k)
{
	int x,y,p,h=0;
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=k;i<=m&&h<n-1;i++)
	{
		x=find(a[i].l);y=find(a[i].r);
		if(x!=y) f[y]=x,p=a[i].o-a[k].o,h++;
	}
	if(h>=n-1) s=min(s,p);
}
int main()
{
	freopen("slight-tree.in","r",stdin);
	freopen("slight-tree.out","w",stdout);
	while(scanf("%d%d",&n,&m),n||m)//n,m可以有一个为0
	{
		s=10000000;
		//if(m<n-1) goto hehe;不能写在这,不然输入不完
		for(int i=1;i<=m;i++)
			scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].o);
		if(m<n-1) goto hehe;
		sort(a+1,a+1+m);
		for(int i=1;m-i+1>=n-1;i++)//循环最小边
			kus(i);
	hehe:
		if(s==10000000) s=-1;
		printf("%d\n",s);
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值