zoukankan      html  css  js  c++  java
  • 最小生成树——普利姆 克鲁斯卡尔

    最小生成树

    生成树定义:是原图的一个极小连通子图,含有原图的全部顶点,但只有n-1条边。它连通但边只有n-1,也就是说任意让两点连边必定成环,不过这结论好像没啥用。

    最小生成树:对于一张图的生成树可能有多种,对于边权和最小的一种就是最小生成树了。

    prim算法

    首先首先,我们来几个标识,原图是N={V,E},TE是N上最小生成树的边集,然后有个已选点集合U,那么未选点集就是V-U了。

    首先,先来个起点,加入到U里面。

    然后,在所以边(a,b),a属于U,b属于V-U里面,找一条权值最小的边(a0,b0),并入集合TE,同时b0加入到U。

    最终,经过不断的重复,U越来越多,达到n个点,也就是说U=V;

    证明:    设prim生成的图为G0

        假设存在Gmin使得cost(Gmin)<cost(G0),则Gmin存在边<u,v>不属于G0;

        将<u,v>加入Gmink可以得到一个环,且<u,v>不是该环的最长边

        这与prim每次生成最短边矛盾;

    prim的实现:

      首先会用到closedge结构体数组,包括adjvex域和lowcost域;

    struct node{
        int adjvex;
        int lowcost;
    }closedge[maxn];   //closedge[i]中,i和closedge[i].adjvex组成一条边,权值为lowcost
    //整个数组用于记录U到V-U具最小权值的边,每次选最小的就是上面的步骤二了

     先来看看算法分析:

    struct node{
        int adjvex;
        int lowcost;//一开始初始化为正无穷
    }closedge[maxn];   //closedge[i]中,i和closedge[i].adjvex组成一条边,权值为lowcost
    //整个数组用于记录U到V-U具最小权值的边,每次选最小的就是上面的步骤二了
    
    void prim(AMGRAPH G, char u)
    {//以名字u为起点,在邻接矩阵表示的图G中,构建最小生成树
        int k=locate(G,u);//寻找u名字在定点表中的下标
        for(int i=0;i<n;i++)
            if(i!=k) closedge[i]={u,G.arc[k][j]};//对起点的邻接点初始化
        closedge[k].lowcost =0;//为0就代表着选过了
        for(int i=1;i<G.vexnum;i++)
        {//选择其余n-1个点
            k=min(closedge);//找出closedge中lowcost最小的编号(下标)
            int u0=closedge[k].adjvex;//属于U
            int v0=G.vexs[k];         //属于V-U
            cout<<u0<<v0;
            closedge[k].lowcost =0;
            for(int j=0;j<G.vexnum;++j){
                if(G.arc[k][j]<closedge[j].lowcost )
                    closedge[j].lowcost=G.arc[k][j];
                    closedge[j].adjvex =G.vex[k];
            }
        }
    }

    正式代码:

     1 struct node{
     2     int adjvex; /*或者*/ char adjvex;//一个是编号一个是名字
     3     int lowcost;//一开始初始化为正无穷
     4 }closedge[maxn];   //closedge[i]中,i和closedge[i].adjvex组成一条边,权值为lowcost
     5 //整个数组用于记录U到V-U具最小权值的边,每次选最小的就是上面的步骤二了
     6 
     7 int locate(AMGRAPH G,char u)
     8 {
     9     for(int i=0;i<=n;i++)
    10         if(G.vexs[i]==u)
    11             return i;
    12 }
    13 
    14 void prim(AMGRAPH G, char u)
    15 {//以名字u为起点,在邻接矩阵表示的图G中,构建最小生成树
    16 
    17     int k=locate(G,u);//寻找u名字在定点表中的下标
    18     for(int i=0;i<n;i++)
    19         if(i!=k) closedge[i]={u,G.arc[k][j]};//对起点的邻接点初始化
    20     closedge[k].lowcost =0;//为0就代表着选过了
    21     
    22     for(int i=1;i<G.vexnum;i++)
    23     {//选择其余n-1个点
    24     
    25         int temp=INT-MAX,k;
    26         for(int j=0;j<G.vexnum;j++)
    27         {
    28             if(closedge[j].lowcost<temp&&closedge[j].lowcost!=0)//对于未选的最小权值的点
    29                 k=j;
    30         }
    31         
    32         char u0=closedge[k].adjvex;//属于U
    33         char v0=G.vexs[k];         //属于V-U
    34         cout<<u0<<v0;
    35         
    36         closedge[k].lowcost =0;//标记k,代表选了
    37         
    38         for(int j=0;j<G.vexnum;++j){//刷新与k的邻接点,若权值比lowcost小,就刷新
    39             if(G.arc[k][j]<closedge[j].lowcost )
    40                 closedge[j].lowcost=G.arc[k][j];
    41                 closedge[j].adjvex =G.vex[k];
    42         }
    43     }
    44 }
    View Code

     好啦,这算是彻底领悟了,那就看看更简单的克鲁斯卡尔算法;

    克鲁斯卡尔

     与prim不同,kruskal是加边

    与课本不同,这是另一种描述,很像哈夫曼树

    克鲁斯卡尔算法:先构造一个只含n个顶点,但边集为空的子图,

            把子图中各个顶点看成各棵树的根节点,之后,

            从边集选权值最小的边,若该边的两个顶点属于不同的树

            就加进子图,把两棵树并成一颗,以此类推,直至森林中只有一棵树。

    这个定义本质还是不断让两个连通分量变成一个,知道子图连通。

    证明:这这这显而易见啊,不过还是要证的,网上的术语好烦,这里以后补。

    那么如何判断一条边连的是不是两个不同的连通分量呢?

    如果是同一连通分量,那么加入这条边就一定成环,我们可以判断它成不成环

    或者直接判断属不属于同一个连通分量,答案呼之欲出——并查集

    代码:

     1 struct node{
     2     int u,v,w;
     3 }map[maxn];
     4 
     5 bool cmp(node a,node b)
     6 {
     7     return a.w <b.w ;
     8 }
     9 
    10 int find(int x)
    11 {
    12     return x==p[x]?x:p[x]=find(p[x]);//并查集,一直找掌门,并将中间赋值
    13 }
    14 
    15 void kelusikaer (node map[] ,int u)
    16 {//对图map图,计算最小生成树的边权和
    17     int p[maxn];
    18     for(int i=0;i<n;i++)
    19         p[i]=i;
    20     /*这里输入数据*/
    21     sort(map,map+k,cmp);//k为边的数目
    22     int ans=0;
    23     for(int i=0;i<k;i++)
    24     {
    25         int x=find(map[i].u );
    26         int y=find(map[i].v );
    27         if(x!=y){//如果不等,就代表不是同一个连通分量
    28             ans+=map[i].w ;
    29             p[x]=y;//代表合并了两个连通分量
    30         }
    31     }
    32     cout<<ans<<endl;
    33 }
    View Code

       实践和作业比较简单就不弄了,作业是最简单的遍历,实践上一个基本的深搜或广搜就完事了,最短路径还没写完就先来个最小生成树。

       省赛混了个银还是很爽的,然后之后学什么呢,拓扑排序,强联通还有,把主席树补了,vj补补题转战洛谷。

      

        

  • 相关阅读:
    深入理解幂等性(转)
    Mysql数据库表结构设计准则
    nodejs以对象做对象的key导致value一直被覆盖
    数组常用方法
    css选择器总结
    CSS 选择器权重计算规则
    ElementUI select
    linux-sed命令
    vue 组件数据传递:
    vue视频: 自定义指令 && 拖拽 && 自定义键盘信息
  • 原文地址:https://www.cnblogs.com/qq2210446939/p/10867459.html
Copyright © 2011-2022 走看看