最小生成树(MST):权值最小的生成树。
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
最小生成树,顾名思义,它的权值是某个连通图中最小的,要是掌握了它,我们就可以用它来解决一些连通代价
问题。例如,现在要修公路连通N个村子,两个村子之间可能有多种修建方案,我们知道每种方案对应的村子和距离,现在要计算至少要修建多长的公路。
1.在一个有N个点的连通图中,边的数量一定大于等于N-1,而等于N-1这种情况,就是连通图的最小生成树。
2.N个点用N-1条边连通,形成的图只可能是树。
因此,要在一个连通图中找到最小生成树,需要满足:
1. 相同端点的边优先取长度小的边
2. 用N-1条边连接N个点(没有回路)
kruskal算法就是以边为核心的算法,它每次用一条边连接两个点(其中一个点在树中,另一个点在树外,或者两个点都在树外),只要边的数量达到了N-1,那么最小生成树就形成了。
要满足条件一,只需要用sort按边长排序即可
要满足条件二(没有回路),即两个已经连通的点不需要再连接。这里我们可以用并查集的根结点的性质判断两个点是否已经在同一连通图中。
举个例子,现在有n个村子,要修建公路连通所有村子(只需保证从a村能到达b村即可,途中可经过c村),给你m组某两个村子间的可修建方案距离,问:至少要修建多少千米的公路?
先输入两个整数n、m。(n表示有村子数量,m表示有方案数量。)然后在接下来的m行输入三个整数a,b,l,表示在该方案中,a村子和b村子的修建公路长度为l。
样例输入:
4 5
1 2 4
1 3 3
1 3 2
3 4 1
2 3 5
样例输出
7
首先,输入有五种方案
对应的是
① 1 2 4
② 1 3 3
③ 1 3 2
④ 3 4 1
⑤ 2 3 5
我们先按两两村子间的方案距离排序,得到
④ 3 4 1
③ 1 3 2
② 1 3 3
① 1 2 4
⑤ 2 3 5
接下来就是连通的过程,显然④肯定要取,它的两个端点都在树外,并且距离最小。然后看③,③中有端点1、3,其中3在树内,1在树外,也满足条件。然后看②,①在树内,③也在树内,这时取了这组方案就会产生回路,因此这种方案不能取。再看①,1在树内,2在树外,满足条件。这时我们已经用④③①三条边连接了四个村子,即满足了用N-1条边连接N个结点
的情况,最小生成树已经产生,可以直接跳过下面的方案。
最终答案就是1+2+4=7
代码实现如下:
#include <algorithm>
#include <iostream>
using namespace std;
#define N 100005
int pre[N];
struct line
{
int a, b, len; //记录结点a到结点b的距离len
} p[N];
int find(int x) //查找x的根节点并且完成路径压缩
{
if (x == pre[x])
return x;
return pre[x] = find(pre[x]);
}
bool cmp(line a, line b)
{
return a.len < b.len; //按line的边长排序
}
int main()
{
int n, m, ans = 0;
cin >> n >> m;
for (int i = 1; i <= n; i++)
pre[i] = i; //初始化每个结点为独立的图
for (int i = 1; i <= m; i++)
cin >> p[i].a >> p[i].b >> p[i].len;
sort(p + 1, p + 1 + m, cmp);
int ct = 0; //记录边的数量
for (int i = 1; i <= m; i++)
{
if (find(p[i].a) != find(p[i].b))
{
pre[find(p[i].a)] = find(p[i].b);
ans += p[i].len;
ct++;
}
if (ct == n - 1) //如果边的数量已经达到了n-1条,直接break退出
break;
}
cout << ans << endl;
return 0;
}