• 树:最小生成树-两种算法


    先来说说什么是树。

    树实际上是图的一种,当一个有N个点的无向连通图,只有N-1条边时,就是一棵树,即树中不会有环出现;所以对于一个图,删除某些环中的某条边,使得该图成为一棵树,那么这棵树就称为生成树。

    而最小生成树的意思就是,给定有n个顶点的带权图G(E,V),找到一棵生成树,求该生成树的边权和。


    Kruskal算法:

    算法步骤:

          1.构造一个有n个顶点的无边子图;

          2.从原图选择边权最小的边加入该子图,直至子图成为一棵树;

          3.边能加入子图的条件是,边的两个端点u,v还未连通,Kruskal算法中运用并查集的查询来询问两个顶点是否连通;

          Kruskal算法的本质是,通过树的合并(不断加边,构成子树),来构建完整的生成树。

    Kruskal+邻接表 模板如下:

    //复杂度O(ElogE),E为边数
    const int INF=10e8;
    const int MAXN=110;
    const int MAXM=MAXN*MAXN;
    
    struct edge
    {
        int u,v,cost;
        bool operator < (const edge &a)const
        {
            return cost<a.cost;
        }
    };
    
    edge E[MAXN];
    int couEdge;  //记得初始化couEdge=0,然后进行加边操作
    int fa[MAXN];
    
    int Find(int x)
    {
        if(fa[x]==-1)  //important!!
            return x;
        fa[x]=Find(fa[x]);
        return fa[x];
    }
    
    int Kruskal(int n)
    {
        int ans=0int cou=0;  //important!! 建好子图之后,每给子图加上一条边,cou+1
        int u,v,cost,t1,t2;
        
        memset(fa,-1,sizeof(fa));  //important!!
        sort(E,E+couEdge);
        for(int i=0;i<couEdge;i++)
        {
            u=E[i].u;v=E[i].v;cost=E[i].cost;
            t1=Find(u);t2=Find(v);  //t1,t2分别代表u,v的父节点
            
            if(t1!=t2)
            {
                ans+=cost;
                fa[t1]=t2;
                cou++;
            }
            if(cou==n-1)
                break;
        }
        if(cou<n-1)
            return -1;  //表示该图不连通
        return ans;
    }
    //加边操作
    void add_edge(int u,int v,int cost)
    {
        E[couEdge].u=u;E[couEdge].v=v;E[couEdge].cost=cost;
    }


    Prim算法:

          前面说了,Kruskal算法是通过树的合并来构建生成树,而现在要说的Prim算法,其本质则是,从一个顶点扩展成生成树,复杂度O(N2)。

    【对比:Kruskal和Prim的异同】

                同:都是贪心思想;都是选择边权最小的边;

                异:Kruskal是优先选择边权最小的边,Prim是从点集出发选择边权最小的边;

                      //当题目中的数量比顶点的时候,用Prim速度比较快。


    Prim算法的基本步骤:

          1.初始化点集 V={x};

          2.找到边(u,v)满足:u∈点集V,v不∈点集V

          3.选取2.中满足条件的边中最小的一条加入生成树,并把v加入点集V,重复执行,直至原图所有的点都加入点集V,就得到了一棵最小生成树。

       Tips:关于如何快速找到可以添加到生成树的边:

                        可以维护一个数组lowcost[i…j]记录点集V到各个顶点的最小边,即可快速找到边,且每当有新的点加入点集V时,该数组都要更新一次

    Prim+邻接矩阵 模板如下:

    const int INF=10e8;
    const int MAXN=110;
    
    bool vis[MAXN];
    int lowcost[MAXN];
    
    int Prim(int cost[][MAXN],int n)
    {//输入cost[][],n;且cost[][]的初始化要注意没连通的边存INF
        int ans=0;
        int minn,k;
    
        memset(vis,0,sizeof(vis));
        vis[1]=1;  //从点1开始扩展,找距离1最小的边扩展
    
        for(int i=1;i<=n;i++)
            lowcost[i]=cost[1][i];  //
        for(int i=1;i<=n-1;i++)  //循环n-1次,因为vis[1]=1
        {
            minn=INF;k=-1;
            for(int j=1;j<=n;j++)
                if(!vis[j]&&minn>lowcost[j])
                {
                    minn=lowcost[j];
                    k=j;
                }
            if(k==-1) break;  //不连通
            ans+=minn;vis[k]=1;  //再从k点开始扩展,准备更新lowcost[]
            for(int j=1;j<=n;j++)
                if(!vis[j]&&lowcost[j]>cost[k][j])
                    lowcost[j]=cost[k][j];
        }
        return ans;
    }
  • 相关阅读:
    shell 基础进阶 *金字塔
    shell,awk两种方法写9*9乘法表
    shell脚本判断一个用户是否登录成功
    shell 冒泡算法 解决数组排序问题
    shell 石头剪刀布
    应用shell (ssh)远程链接主机
    nmcli命令使用
    光盘yum源autofs按需挂载
    LVM扩容,删除
    LVM创建
  • 原文地址:https://www.cnblogs.com/atmacmer/p/5178507.html
Copyright © 2020-2023  润新知