最小生成树(一)kruskal

今天写一篇关于最小生成树的番外篇,以前写最小生成树总是用的prim,关于kruskal只是知道一些原理,一直也没有时间去学,今天偶然看了一些并查集,才想起了这个算法

会想起刚刚(预)学过的数据结构,来解释一下它的原理:

先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。-------百度百科

通俗一点讲,给定加权无向图G(E,V),将所有边取出只留下点集,然后边按权值从小到大排序后,加入点集中对应该条边原本连接的点的关系,每加入一条边,都要检查加入这条边后是否会与之前加入的边构成环,如果成环,则该边不可取,进行下一条边的的判断,当加入n-1(图有n个顶点)条边后,最小生成树毕.

证明(摘自百度百科):

  1. 这样的步骤保证了选取的每条边都是桥,因此图G构成一个树。
  2. 为什么这一定是最小生成树呢?关键还是步骤3中对边的选取。算法中总共选取了n-1条边,每条边在选取的当时,都是连接两个不同的连通分量的权值最小的边
  3. 要证明这条边一定属于最小生成树,可以用反证法:如果这条边不在最小生成树中,它连接的两个连通分量最终还是要连起来的,通过其他的连法,那么另一种连法与这条边一定构成了环,而环中一定有一条权值大于这条边的边,用这条边将其替换掉,图仍旧保持连通,但总权值减小了。也就是说,如果不选取这条边,最后构成的生成树的总权值一定不会是最小的。

时间复杂度:(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;
}

 

posted @ 2020-02-09 20:49  remarkableboy  阅读(170)  评论(0编辑  收藏  举报