最小生成树Kruskal算法+并查集实现

今天刚掌握Kruskal算法,写下随笔。

对于稀疏图来说,用Kruskal写最小生成树效率更好,加上并查集,可对其进行优化。

Kruskal算法的步骤:

1.对所有边进行从小到大的排序。

2.每次选一条边(最小的边),如果如果形成环,就不加入(u,v)中,否则加入。那么加入的(u,v)一定是最佳的。

并查集:
我们可以把每个连通分量看成一个集合,该集合包含了连通分量的所有点。而具体的连通方式无关紧要,好比集合中的元素没有先后顺序之分,只有“属于”与“不属于”的区别。图的所有连通分量可以用若干个不相交集合来表示。

而并查集的精妙之处在于用数来表示集合。如果把x的父结点保存在p[x]中(如果没有父亲,p[x]=x),则不难写出结点x所在树的递归程序:

find(int x) {return p[x]==x?x:p[x]=find(p[x]);}

意思是,如果p[x]=x,说明x本身就是树根,因此返回x;否则返回x的父亲p[x]所在树的根结点。

既然每棵树表示的只是一个集合,因此树的形态是无关紧要的,并不需要在“查找”操作之后保持树的形态不变,只要顺便把遍历过的结点都改成树根的儿子,下次查找就会快很多了。如下图所示:

设第i条边的端点序号和权值分别保存在u[i],v[i],w[i]中,而排序后第i小的边保存在r[i]中。(间接排序是指排序的关键字是对象的代号,而不是对象本身。)
结合hdoj1863代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#define N 150
using namespace std;
int m,n,u[N],v[N],w[N],p[N],r[N];
int cmp(const int i,const int j) {return w[i]<w[j];}
int find(int x) {return p[x]==x?x:p[x]=find(p[x]);}
int kruskal()
{
	int cou=0,x,y,i,ans=0;
	for(i=0;i<n;i++) p[i]=i;
	for(i=0;i<m;i++) r[i]=i;
	sort(r,r+m,cmp);
	for(i=0;i<m;i++)
	{
		int e=r[i];x=find(u[e]);y=find(v[e]);
		if(x!=y) {ans += w[e];p[x]=y;cou++;}
	}
	if(cou<n-1) ans=0;
	return ans;
}

int main()
{
	int i,ans;
	while(scanf("%d%d",&m,&n)!=EOF&&m)
	{
		for(i=0;i<m;i++)
		{
			scanf("%d%d%d",&u[i],&v[i],&w[i]);
		}
		ans=kruskal();
		if(ans) printf("%d\n",ans);
		else printf("?\n",ans);
	}
	return 0;
}
posted @ 2011-04-29 09:37  Veegin  阅读(12470)  评论(3编辑  收藏  举报