最小生成树–kruskal
也加克鲁斯卡尔算法。
kruskal算法的思想简单说来就是:每次选择图中最小边权的边,如果边两端的顶点在不同的连通块中,就把这条边加入最小生成树中。
如果是稠密图(边多),用prim算法;如果是稀疏图(边少),用kruskal算法,或者用我自己想的“做减法”。
//对于kruskal算法来说,由于需要判断边的两个端点
//是否在不同的连通块中,因此边的两个端点的编号是一定
//需要的;而算法中有涉及边权,因此边权也必须要有
struct edge{
//边的两个端点编号
int u,v;
//边权
int cost;
}E[MAXE];
//需要写一个排序函数来让数组E按边权从小到大排序,
//因此可以自定义sort的cmp函数
bool cmp(edge a,edge b)
{
return a.cost<b.cost;
}
//kruskal算法伪代码模板
int kruskal()
{
令最小生成树的边权之和为ans、最小生成树的当前边数 num_edge;
将所有边按边权从小到大排序;
for( 从小到大枚举所有边 )
{
if( 当前测试边的两个端点在不同的连通块中 )
{
将该测试边加入最小生成树中;
ans+=测试边的边权;
最小生成树的当前边数num_edge+1;
当边数num_edge等于顶点数-1时结束循环;
}
}
}
//伪代码中两处地方很不直观
//1.如何判断测试边的两个端点是否在不同的连通块中;
//2.如何将测试边加入最小生成树
输入
6 10
0 1 4
0 4 1
0 5 2
1 2 1
1 5 3
2 3 6
2 5 5
3 4 5
3 5 4
4 5 3
输出
11
//以上问题需要用到并查集,下面 提供kruskal算法实际应用例程 #include <cstdio> #include <algorithm> using namespace std; const int MAXV=110; const int MAXE=10010; //边集定义部分 struct edge{ //边的两个端点编号 int u,v; //边权 int cost; }E[MAXE]; bool cmp(edge a,edge b) { return a.cost<b.cost; } //并查集部分 //并查集数组 int father[MAXV]; //并查集查询函数 int find_father(int x) { int a=x; while(x!=father[x]) { x=father[x]; } //路径压缩 while( a!=father[a] ) { int z=a; a=father[a]; father[z]=x; } return x; } //kruskal 部分,返回最小生成树的边权之和,参数n为顶点个数, //m为图的边数 int kruskal(int n,int m) { //ans 为边权之和,num_edge为当前生成树的边数 int ans=0; int num_edge=0; for(int i=0;i<n;i++) { //初始化并查集 father[i]=i; } //所有边权按边权从小到大排序 sort(E,E+m,cmp); for(int i=0;i<m;i++) { //查询测试边两个端点所在集合的根结点 int fau=find_father(E[i].u); int fav=find_father(E[i].v); //如果不在一个集合中 if( fau!=fav ) { father[fau]=fav; //合并集合(就是把测试边加入到最小生成树中) //边权之和增加测试边的边权 ans+=E[i].cost; //当前生成树的边树加1 num_edge++; //边树等于顶点数减1时结束算法 if( num_edge == n-1 ) { break; } } } //无法连通时返回-1 if( num_edge !=n-1 ) { return -1; } else { //返回最小生成数的边权之和 return ans; } } int main() { int n,m; //顶点数、边数 scanf("%d%d",&n,&m); for(int i=0;i<m;i++) { //两个端点编号,边权 scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].cost); } int ans= kruskal(n,m); printf("%d\n",ans); return 0; }