之前我写过一个最小生成树的prim算法,当时我说过还有另一种求最小生成树的算法,那就是今天要说的——克鲁斯卡尔,如果想学习普林姆的可以翻看我前面写的博客文章http://blog.csdn.net/monkey0le/article/details/7725547。
前面说过,prim算法的思想是把点加入到另一个集合中,而kruskal恰好相反,它的思想很简单,不断找最小的边加入进去,并且保证加入边后,仍然是一棵树而不是图,即不存在回路。这里就需要用到上一篇文章中介绍过的并查集,用并查集就可以判断加入边的两个顶点是否已经在这棵树上了。
为了保存边的权值和边的两个顶点,我们定义一个结构体:
typedef struct Edge
{
int star;
int end;
int value;
}Edge;
Edge path[MAXN];
为了找到最小的权值,我们用sort()进行一次排序,sort()函数的用法请参考:http://blog.csdn.net/monkey0le/article/details/7948011
下面是kruskal的核心代码:
int Kruskal (int n)
{
init (); //初始化并查集
int x = n * (n - 1) / 2;
sort (path,path + x);
int sum = 0;
int count = 0;
for (int i = 0; i < x && count < n; i++)
{
int a = path[i].star;
int b = path[i].end;
if (FindRoot (a) != FindRoot (b)) //不在同一个集合
{
Union(a,b); //合并
sum += path[i].value; //加入边
count ++; //统计边
}
}
return sum;
}
下面是HDOJ-1233题用kruskal实现的代码,我第一次做这题是用prim写的,当时用了93MS,后来用kruskal写用了140MS,但是并不是说prim算法会优于kruskal,只是两种算法适用于不同的图。prim算法的时间复杂度不依赖于排序算法,并且与点的个数有关,适用于稠密图,而kruskal适用于稀疏图。
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 100 + 50;
int father[MAXN];
typedef struct Edge
{
int star;
int end;
int value;
const bool operator < (const struct Edge &old) const
{
return value < old.value;
}
}Edge;
Edge path[MAXN * MAXN];
int FindRoot (int n)
{
while (father[n] != n)
{
n = father[n];
}
return n;
}
void inline Union (int a, int b)
{
a = FindRoot (a);
b = FindRoot (b);
father[b] = a;
}
void init ()
{
for (int i = 0; i < MAXN; i++)
{
father[i] = i;
}
}
int Kruskal (int n)
{
init ();
int x = n * (n - 1) / 2;
sort (path,path + x);
int sum = 0;
int count = 0;
for (int i = 0; i < x && count < n; i++)
{
int a = path[i].star;
int b = path[i].end;
if (FindRoot (a) != FindRoot (b))
{
Union(a,b);
sum += path[i].value;
count ++;
}
}
return sum;
}
int main()
{
int n;
while (scanf("%d",&n) != EOF && n)
{
int x = n * (n - 1) / 2;
for (int i = 0; i < x; i++)
{
scanf("%d%d%d",&path[i].star,&path[i].end,&path[i].value);
}
cout<<Kruskal(n)<<endl;
}
return 0;
}