最小生成树
- 图:由顶点和边组成,任何两个顶点之间都可以有边
- 树:n个顶点n-1条边,并且是连通的
- 生成树:如果一个树包含了某个图的所有顶点,并且包含了其中的n-1条边,并且是连通的
- 最小生成树:边的权值和最小
实现最小生成树有两种方法:
- Prim算法
- Kruskal算法——并查集
这里介绍的是Kruskal算法(克鲁斯卡尔算法)
-
MST性质(最小生成树性质)
- 对于一个连通图,至少存在一棵最小生成树,包含了最短的边。
实现步骤:
- 把原始图的 N 个节点看成 N 个独立子图;
- 每次选取当前最短的边,看两端是否属于不同的子图(保证不会构成回路);若是,加入;否则,放弃;
- 循环操作步骤2,直到有 N − 1 条边。
下面看一道题:
题目描述:
地图上有 n 个城市,现在想给这 n 个城市之间造路,希望能让城市之间两两可达。给出了m 种供选择的道路,每种选择是一个三元组 (u, v, w) ,代表给 u 城市和 v 城市之间建造一条长度为 w 的道路。
要求最终的道路总长越小越好。
显然就是一道最小生成树的题目,直接套模板即可。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
struct edge
{
int u,v,w;
}e[10005];
bool cmp(edge a,edge b) //按边从小到大排序
{
return a.w<b.w;
}
//并查集确定有无回路
int father[10005];
int findx(int x)
{
int r=x;
while(father[r]!=r)
r=father[r];
return r;
}
void merge(int x,int y)
{
int fx=findx(x);
int fy=findx(y);
if(fx!=fy)
father[fx]=fy;
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>e[i].u>>e[i].v>>e[i].w;
sort(e+1,e+m+1,cmp); //把边按边权排序
for(int i=1;i<=n;i++)
father[i]=i;
int res=0; //记录添加到最小生成树中的边数 最终等于n-1
int ans=0; //记录边权值和
for(int i=1;i<=m && res<n-1;i++)
{
int u=e[i].u;
int v=e[i].v;
if(findx(u)==findx(v)) //若产生回路,不加这条边
continue;
else //否则加这条边
{
res++; //记录最小生成树中的边数
merge(u,v);
ans+=e[i].w;
}
}
cout<<ans<<endl;
return 0;
}