最小生成树二·Kruscal算法

随着小Hi拥有城市数目的增加,在之间所使用的Prim算法已经无法继续使用了——但是幸运的是,经过计算机的分析,小Hi已经筛选出了一些比较适合建造道路的路线,这个数量并没有特别的大。

所以问题变成了——小Hi现在手上拥有N座城市,且已知其中一些城市间建造道路的费用,小Hi希望知道,最少花费多少就可以使得任意两座城市都可以通过所建造的道路互相到达(假设有A、B、C三座城市,只需要在AB之间和BC之间建造道路,那么AC之间也是可以通过这两条道路连通的)。
提示:积累的好处在于可以可以随时从自己的知识库中提取想要的!
提示:积累的好处在于可以可以随时从自己的知识库中提取想要的!

小Hi的温馨提示:边的费用在某些时候可以被理解成为边的长度、边的权值等等!所以在后文中各种可能会用上述各种称呼来描述费用一事。

小Ho听到了这个问题,发表了感慨:“这不就是之前最短路问题的时候针对点集变大,但是边集很小的稀疏图么?和SPFA算法当时遇到的问题很像诶!”

小Hi严肃道:“是的!在现实生活中,大部分的图其实都是稀疏图,哪怕不是,也可以像我这种通过筛选的方式转化为稀疏图!所以稀疏图上的问题是非常重要的!”

“是的!”小Ho连连称是,继续道:“那难道这题也像SPFA那样子来做么?但是最小生成树似乎是不可以用宽度优先搜索来解决的啊?”

“倒也没有那么复杂。”小Hi道:“还记的我们在Prim算法中得出的结论——对于城市i(i≠1),如果i与城市1的距离不超过其他任何城市j(j≠1)与城市1的距离,那么(1, i)这一条边一定存在于某一棵最小生成树中么?”

“自然记得。”

“我们来把这个结论稍微改一下:图中最短的边一定属于某棵最小生成树。”小Hi说道:“证明是简单的——因为城市1的标号是随意的,也就是无论给哪个城市标1都会有之前的结论,那么对于任意节点来说它连接的所有边中最短的边一定存在于某一棵最小生成树中,而整个图中最短的一条边一定是这样的一条边。”

小Ho道:“是的,那么也就是说我只需要不断的找到当前图中最短的一条边(一开始就将所有的边排序然后从小到大)——这样的一条边一定属于某棵最小生成树!然后我只需要将连接的2个节点合并成为一个新的节点,那么这个问题就变成了一个规模-1的问题了!只需要不断重复这样的操作,就能够使得问题最后变成1的规模,这个时候只需要之前找到的所有一定存在于最小生成树中的边的费用加起来就是答案了就可以了!”

小Hi点了点头,说道:“那么还剩一个问题,在Prim问题中,由于合并都是和1号城市合并,所以只需要简单的记录一个节点是否已经合并进了1号城市就可以了,但是在这里却会复杂很多,你有什么想法么?”

“你这就太小看我的记忆力了!”小Ho抱怨道:“在当年遇到黑叔叔的时候,我们不是学会了一种并查集的方法么?在这里只需要用并查集维护哪些节点被合并到了一起,不就行了?”

“嗯~ o( ̄▽ ̄)o,算你聪明,赶紧去实现程序吧!”
输入

每个测试点(输入文件)有且仅有一组测试数据。

在一组测试数据中:

第1行为2个整数N、M,表示小Hi拥有的城市数量和小Hi筛选出路线的条数。

接下来的M行,每行描述一条路线,其中第i行为3个整数N1_i, N2_i, V_i,分别表示这条路线的两个端点和在这条路线上建造道路的费用。

对于100%的数据,满足N<=10^5, M<=10^6,于任意i满足1<=N1_i, N2_i<=N, N1_i≠N2_i, 1<=V_i<=10^3.

对于100%的数据,满足一定存在一种方案,使得任意两座城市都可以互相到达。
输出

对于每组测试数据,输出1个整数Ans,表示为了使任意两座城市都可以通过所建造的道路互相到达至少需要的建造费用。
Sample Input

5 29
1 2 674
2 3 249
3 4 672
4 5 933
1 2 788
3 4 147
2 4 504
3 4 38
1 3 65
3 5 6
1 5 865
1 3 590
1 4 682
2 4 227
2 4 636
1 4 312
1 3 143
2 5 158
2 3 516
3 5 102
1 5 605
1 4 99
4 5 224
2 4 198
3 5 894
1 5 845
3 4 7
2 4 14
1 4 185

Sample Output

92
#include<stdio.h>
typedef struct 
{
	int x;
	int y;
	int z;
}tree;
tree a[1000010], t;//定义结构体数组用来储存各个点之间的距离关系 
int f[100010], sum=0, count=0;
void quick(int left, int right)//快排 
{
	int i = left;
	int j = right;
	int temp = a[left].z;
	if(left>right)
	return ;
	while(i!=j)
	{
		while(i<j&&a[j].z>=temp)
		j--;
		while(i<j&&a[i].z<=temp)
		i++;
		if(i<j)
		{
			t=a[i];
			a[i]=a[j];
			a[j]=t;
		}
	}
	t=a[left];
	a[left]=a[i];
	a[i]=t;
	quick(left,i-1);
	quick(i+1,right);
	return;
}
//并查集,查找两个点之间是否连通 
int get(int x)
{
	if(f[x]==x)
	return x;
	else
	{
		f[x]=get(f[x]);
		return f[x];
	}
}
int merge(int x, int y)
{
	int t1, t2;
	t1=get(x);
	t2=get(y);
	if(t1!=t2)
	{
		f[t2]=t1;
		return 1;
	}
	return 0;
}
int main()
{
	int n, m, i;
	scanf("%d%d",&n,&m);
	for(i=1; i<=m; i++)
	scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
	quick(1,m);
	for(i=1; i<=n; i++) 
		f[i]=i;
	for(i=1; i<=m; i++)
	{
		if(merge(a[i].x,a[i].y))//如果两点之间未联通 
		{
			count++;
			sum+=a[i].z;
		}
		if(count==n-1)//当所有边都加入后 
		break;
	}
	printf("%d\n",sum);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值