zoukankan      html  css  js  c++  java
  • P3366 【模板】最小生成树

    kruskal

    1.基本思想(可忽略)

    先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。 
     
    2.步骤(可忽略)
    (1).新建图G,G中拥有原图中相同的节点,但没有边;
    (2).将原图中所有的边按权值从小到大排序;
    (3).从权值最小的边开始,如果这条边连接的两个节点于图G中不在同一个连通分量中,则添加这条边到图G中;
    (4).重复3,直至图G中所有的节点都在同一个连通分量中。
     
    3.证明(可忽略)
    1. 这样的步骤保证了选取的每条边都是桥,因此图G构成一个树。
    2. 为什么这一定是最小生成树呢?关键还是步骤3中对边的选取。算法中总共选取了n-1条边,每条边在选取的当时,都是连接两个不同的连通分量的权值最小的边
    3. 要证明这条边一定属于最小生成树,可以用反证法:如果这条边不在最小生成树中,它连接的两个连通分量最终还是要连起来的,通过其他的连法,那么另一种连法与这条边一定构成了环,而环中一定有一条权值大于这条边的边,用这条边将其替换掉,图仍旧保持连通,但总权值减小了。也就是说,如果不选取这条边,最后构成的生成树的总权值一定不会是最小的。
    4.时间复杂度(可忽略)
    平均时间复杂度为O(|E|log|E|),其中E和V分别是图的边集和点集。
    题目

    题目描述

    如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz

    输入输出格式

    输入格式:

    第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)

    接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi

    输出格式:

    输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz

    输入输出样例

    输入样例#1: 复制
    4 5
    1 2 2
    1 3 2
    1 4 3
    2 3 4
    3 4 3
    输出样例#1: 复制
    7

    说明

    时空限制:1000ms,128M

    数据规模:

    对于20%的数据:N<=5,M<=20

    对于40%的数据:N<=50,M<=2500

    对于70%的数据:N<=500,M<=10000

    对于100%的数据:N<=5000,M<=200000

    样例解释:

    所以最小生成树的总边权为2+2+3=7

    分析

    以上我全部都是从百度上复制的,所以比较难懂,然而总的来说就是把一颗没有边的树,把权值小的边一个一个加上去而已,如果两个点已经在一个集合,那么不加上这条边。

    题解

    #include<iostream>
    #include<algorithm>
    using namespace std;
    int n,m,cnt,ans,fa[400005];
    struct zxj{
        int u,v,w;//u起点,v中点,w权值 
    }e[400005];
    void init(){//每个的祖先都是自己 
        for(int i=1;i<=n;i++) fa[i]=i;
    }
    int find(int x){//寻找祖先 
        if(fa[x]==x) return x;
        return find(fa[x]);
    }
    void merge(int x,int y){//合并两个集合 
        x=find(x),y=find(y);
        if(x<y) fa[y]=x;
        else fa[x]=y;
    }
    bool cmp(zxj x,zxj y){//排序很重要,这样就可以得出最小的生成树 
        return x.w<y.w;
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
        }
        init();//初始化 
        sort(e+1,e+m+1,cmp);//排序 
        for(int i=1;i<=m;i++){
            if(find(e[i].u)!=find(e[i].v)){//如果两点不在一个集合 
                ans+=e[i].w;//加上权值 
                merge(e[i].u,e[i].v);//加上这条边(合并集合) 
                cnt++;//边数+1 
            }
            if(cnt==n-1) break;//如果边数够,则停止循环 
        }
        printf("%d",ans);
        return 0;
    }
  • 相关阅读:
    每天一道Rust-LeetCode(2019-06-11)
    每天一道Rust-LeetCode(2019-06-10)
    每天一道Rust-LeetCode(2019-06-08)
    每天一道Rust-LeetCode(2019-06-07)
    每天一道Rust-LeetCode(2019-06-06)
    每天一道Rust-LeetCode(2019-06-05)
    每天一道Rust-LeetCode(2019-06-04)
    linux 基本命令
    使用 DrMemory 详细教程
    C++ 虚函数表解析
  • 原文地址:https://www.cnblogs.com/earth833/p/11211123.html
Copyright © 2011-2022 走看看