算法思想:
设G=(V,E)是带权连通图,V={v1,v2,v3......vn}
首先将G的n个顶点看成n个孤立的连通分量,将所有的边从小到大排序,从第一条边开始查看每一个边,只要不连成回路即可
具体实现:
克鲁斯卡尔算法执行的过程中,构造的生成树是由若干个自由树组成的森林,算法在选择一条最小权值的边(u,v)时,必须确信边(u,v)加入后不会形成回路,
这就要求u和v是分别属于生成森林的两棵不同的树上的,否则就选择下一条最小边。通过并查集来实现。
并查集的每个子集合代表一棵自由树,组成的森林就是并查集。事实上,并查集就是用森林表示的,可以利用并查集类的Find成员函数判断一条边
(u,v)的两个顶点是否在同一个子集合中,即是否在一棵树上,若不在同一子集合中,则可以将边(u,v)加入T,否则舍弃之。在将边(u,v)
加入T之后,应将这两个顶点所在的子集合使用union函数合并成一个子集合。
并查集:一种树形的数据结构,用于处理一些不相交的集合的合并和查询,常常在使用中以森林来表示。
(1)查找一个元素所在的集合
查找一个元素所在的集合,只要找到这个元素所在集合的祖先。先设置一个数组Father[x]表示x的父亲的编号,即可以转为寻找
两个元素的最久远的祖先是否相同,可以采用递归实现。
(2)合并两个不相交的集合
找到其中一个集合最久远的祖先,将另外一个集合最久远的祖先指向他就可以
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 1000
struct graph
{
int u,v,cost; //顶点u和顶点V的边权是cost
void set(int a,int b,int w){u=a,v=b,cost=w;}
};
graph d[N*(N+1)/2]; //对于有n个顶点的图,边的数量是e<=n(n+1)/2
int father[N];
int Find(int x)
{
if (father[x]==-1) return x;
return father[x] = Find(father[x]);
}
bool Union(int x, int y)
{ //判断两元素是否属于同一个集合,只要看看他们所在的集合的祖先是否相同
x = Find(x);
y = Find(y);
//祖先相同,是一个集合
if (x==y) return false;
//祖先不同,合并两个集合
if (x>y) father[x] = y;
if (x<y) father[y] = x;
return true;
}
//按照边权的递增顺序排序
int cmp(graph x,graph y)
{
if (x.cost<y.cost) return true;
return false;
}
int main()
{
int n, m;
while (scanf("%d%d", &n, &m)!=EOF && n)
{
int i;
int v,w,edge;
//假设有m条边,每条边描述是(v,w,edge)表示顶点v到顶点w的边权是edge,则构造边集合d如下:
for (i=0; i<m; i++)
{
scanf("%d%d%d", &v, &w, &edge);
d[i].set(v,w,edge);
}
sort(d, d+m, cmp);
memset(father, -1, sizeof(father));
int sum=0; //最小生成树中边权的和
int count=0;
//查找每一条边
for(i=0; i<n*(n+1)/2; i++)
{
//找到一条满足的边
if (Union(d[i].u, d[i].v))
{
printf("%d %d\n", d[i].u, d[i].v);
sum += d[i].cost;
++count;
}
if (count==n-1) break; //成功将所有的顶点连通
}
printf("%d\n",sum);
}
}