最小生成树(一)kruskal
今天写一篇关于最小生成树的番外篇,以前写最小生成树总是用的prim,关于kruskal只是知道一些原理,一直也没有时间去学,今天偶然看了一些并查集,才想起了这个算法
会想起刚刚(预)学过的数据结构,来解释一下它的原理:
先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。-------百度百科
通俗一点讲,给定加权无向图G(E,V),将所有边取出只留下点集,然后边按权值从小到大排序后,加入点集中对应该条边原本连接的点的关系,每加入一条边,都要检查加入这条边后是否会与之前加入的边构成环,如果成环,则该边不可取,进行下一条边的的判断,当加入n-1(图有n个顶点)条边后,最小生成树毕.
证明(摘自百度百科):
-
这样的步骤保证了选取的每条边都是桥,因此图G构成一个树。
-
为什么这一定是最小生成树呢?关键还是步骤3中对边的选取。算法中总共选取了n-1条边,每条边在选取的当时,都是连接两个不同的连通分量的权值最小的边
-
要证明这条边一定属于最小生成树,可以用反证法:如果这条边不在最小生成树中,它连接的两个连通分量最终还是要连起来的,通过其他的连法,那么另一种连法与这条边一定构成了环,而环中一定有一条权值大于这条边的边,用这条边将其替换掉,图仍旧保持连通,但总权值减小了。也就是说,如果不选取这条边,最后构成的生成树的总权值一定不会是最小的。
时间复杂度:(eloge)e为边数,这里一定要分清.
#include <bits/stdc++.h> using namespace std; struct node{ int x; int y; int w; }e[200005]; int f[5055]; int n,m,total; bool camp(node a,node b)//sort()重载函数 { return a.w<b.w; } int find(int x)//并查 { if(f[x]==x) { return x; } else { f[x]=find(f[x]); return f[x]; } } int kruskal() { for(int i=1;i<=m;i++) { int u=find(e[i].x); int v=find(e[i].y); if(u!=v)//如果不在一个集合中 { total+=e[i].w; f[u]=v; n--; if(n==1)//加够了n-1条边 break; } } return total; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) f[i]=i; for(int i=1;i<=m;i++) { int x,y,z; cin>>x>>y>>z; e[i].x=x; e[i].y=y; e[i].w=z; } sort(e+1,e+m+1,camp); kruskal(); if(n==1) cout<<total<<endl; else//不能构成最小生成树 cout<<"orz"<<endl; return 0; }