最小生成树往往用于解决图的连通性问题,树上任意一个结点可以到达另一个结点。假设一个情景:有n个城市,每个城市之间的距离不同,请问最短修多少米的路可以把这些城市连通起来。这就是最基本的一个最小生成树问题。最小生成树主要有两种算法,一种是Prime算法,还有一种是Kruskal算法,思想都是贪心。相较于前者,我更加习惯用后者解决算法问题,或许是这种算法较容易理解也便于实现。说一下两种算法的大致思想。
Prime算法:
第一步:先假设图上的所有点位于集合A中,从图上随机选取一个点,加入到集合B。
第二步:找到集合A中和集合B中相距最短的两个点,将集合A中的那个点,加入到集合B中,重复上述操作,直到集合A中的点全部进入集合B。
大家可以发现这个算法有一些抽象,也不是特别好实现,下面来讲述Kruskal算法:
先将图中所有可以连接边,存储起来,从小到大排序。按顺序从边集中选取边构造最小树,这个过程中要注意的是新加入的和已加入的边不能形成环。那么怎么判断图中是否有环呢,用并查集解决就可以了。所以Kruskal算法实现的核心就是写一个并查集操作。并查集主要包含find和Union两个函数,在此笔者不再赘述,代码解释放注释中了。
#include<iostream>
#include<algorithm>
using namespace std;
int n, m;
const int N = 2e5 + 5;
struct edge
{
int x, y, z; //构造一个边的结构体
};
edge k[N];
//并查集操作判断是否有环
int fa[5005];
int find(int x)
{
if (fa[x] == x) return x; //找这个点的祖先是谁
return fa[x] = find(fa[x]);
}
bool judge(int x, int y) //判断两个点是否已经连通,如果已经连通,则这两个点不再连接
{
int xx = find(x);
int yy = find(y);
return (xx == yy) ? 0 : 1;
}
void Union(int x, int y)
{
fa[find(x)] = find(y); //连通两个点
}
bool cmp(edge a, edge b)
{
return a.z < b.z; //sort函数的排序规则
}
int main()
{
cin >> n >> m;
for (int i = 1;i <= m;i++)
{
int x, y, z;
cin >> x >> y >> z;
k[i].x = x;
k[i].y = y;
k[i].z = z;
} //存储边集
for (int i = 1;i <= n;i++)
{
fa[i] = i; //并查集的初始化操作,每个点祖先都是自己
}
sort(k + 1, k + m + 1, cmp); //对边从小到大进行排序
int cnt = 0; //用于记录加入的边数
int flag = 0; //用于判断是否成功构造出最小生成树
int sum = 0; //记录最小生成树的路劲长度之和
for (int i = 1;i <= m;i++)
{
if (judge(k[i].x, k[i].y))
{
Union(k[i].x, k[i].y);
sum += k[i].z;
cnt++;
}
if (cnt == n - 1) //最小生成树的边数是n-1
{
flag = 1;
break;
}
}
if (flag)
{
cout << sum; //如果构造出最小生成树了,打印路径之和
}
else
{
cout << "无法构造出最小生成树";
}
return 0;
}