• 最小生成树-Prim算法和Kruskal算法


    Prim算法

    1.概览

    普里姆算法Prim算法)。图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中。不但包括了连通图里的全部顶点英语Vertex (graph theory),且其全部边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克英语Vojtěch Jarník发现。并在1957年由美国计算机科学家罗伯特·普里姆英语Robert C. Prim独立发现。1959年,艾兹格·迪科斯彻再次发现了该算法。

    因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。

     

    2.算法简单描写叙述

    1).输入:一个加权连通图。当中顶点集合为V,边集合为E;

    2).初始化:Vnew = {x},当中x为集合V中的任一节点(起始点),Enew = {},为空;

    3).反复下列操作,直到Vnew = V:

    a.在集合E中选取权值最小的边<u, v>,当中u为集合Vnew中的元素。而v不在Vnew集合当中。而且v∈V(如果存在有多条满足前述条件即具有同样权值的边,则可随意选取当中之中的一个);

    b.将v增加集合Vnew中,将<u, v>边增加集合Enew中。

    4).输出:使用集合Vnew和Enew来描写叙述所得到的最小生成树。

     

    以下对算法的图例描写叙述

    图例 说明 不可选 可选 已选(Vnew
     

    此为原始的加权连通图。每条边一側的数字代表其权值。 - - -

    顶点D被随意选为起始点。顶点ABEF通过单条边与D相连。A是距离D近期的顶点。因此将A及对应边AD以高亮表示。 C, G A, B, E, F D
     

    下一个顶点为距离DA近期的顶点。BD为9,距A为7。E为15。F为6。因此,FDA近期,因此将顶点F与对应边DF以高亮表示。

    C, G B, E, F A, D
    算法继续反复上面的步骤。距离A为7的顶点B被高亮表示。

    C B, E, G A, D, F
     

    在当前情况下,能够在CEG间进行选择。CB为8,EB为7,GF为11。E近期。因此将顶点E与对应边BE高亮表示。

    C, E, G A, D, F, B
     

    这里。可供选择的顶点仅仅有CGCE为5。GE为9,故选取C,并与边EC一同高亮表示。

    C, G A, D, F, B, E

    顶点G是唯一剩下的顶点,它距F为11,距E为9,E近期。故高亮表示G及对应边EG G A, D, F, B, E, C

    如今,全部顶点均已被选取,图中绿色部分即为连通图的最小生成树。在此例中,最小生成树的权值之和为39。 A, D, F, B, E, C, G

     

    3.简单证明prim算法

    反证法:如果prim生成的不是最小生成树

    1).设prim生成的树为G0

    2).如果存在Gmin使得cost(Gmin)<cost(G0)   则在Gmin中存在<u,v>不属于G0

    3).将<u,v>增加G0中可得一个环。且<u,v>不是该环的最长边(这是由于<u,v>∈Gmin)

    4).这与prim每次生成最短边矛盾

    5).故如果不成立,命题得证.

     

     

     4.算法代码实现(未检验)

    复制代码
    #define MAX  100000
    #define VNUM  10+1                                             //这里没有ID为0的点,so id号范围1~10
    
    int edge[VNUM][VNUM]={/*输入的邻接矩阵*/};
    int lowcost[VNUM]={0};                                         //记录Vnew中每一个点到V中邻接点的最短边
    int addvnew[VNUM];                                             //标记某点是否增加Vnew
    int adjecent[VNUM]={0};                                        //记录V中与Vnew最邻近的点
    
    
    void prim(int start)
    {
         int sumweight=0;
         int i,j,k=0;
    
         for(i=1;i<VNUM;i++)                                      //顶点是从1開始
         {
            lowcost[i]=edge[start][i];
            addvnew[i]=-1;                                         //将全部点至于Vnew之外,V之内,这里仅仅要对应的为-1,就表示在Vnew之外
         }
    
         addvnew[start]=0;                                        //将起始点start增加Vnew
         adjecent[start]=start;
                                                     
         for(i=1;i<VNUM-1;i++)                                        
         {
            int min=MAX;
            int v=-1;
            for(j=1;j<VNUM;j++)                                      
            {
                if(addvnew[j]!=-1&&lowcost[j]<min)                 //在Vnew之外寻找最短路径
                {
                    min=lowcost[j];
                    v=j;
                }
            }
            if(v!=-1)
            {
                printf("%d %d %d
    ",adjecent[v],v,lowcost[v]);
                addvnew[v]=0;                                      //将v加Vnew
    
                sumweight+=lowcost[v];                             //计算路径长度之和
                for(j=1;j<VNUM;j++)
                {
                    if(addvnew[j]==-1&&edge[v][j]<lowcost[j])      
                    {
                        lowcost[j]=edge[v][j];                     //此时v点增加Vnew 须要更新lowcost
                        adjecent[j]=v;                             
                    }
                }
            }
        }
        printf("the minmum weight is %d",sumweight);
    }
    复制代码

     

    5.时间复杂度

    这里记顶点数v,边数e

    邻接矩阵:O(v2)                 邻接表:O(elog2v)

     

     

     

    Kruskal算法

     

    1.概览

    Kruskal算法是一种用来寻找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪婪算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在同样权值的边时也有效。

     

    2.算法简单描写叙述

    1).记Graph中有v个顶点。e个边

    2).新建图Graphnew,Graphnew中拥有原图中同样的e个顶点,但没有边

    3).将原图Graph中全部e个边按权值从小到大排序

    4).循环:从权值最小的边開始遍历每条边 直至图Graph中全部的节点都在同一个连通分量中

                    if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中

                                             增加这条边到图Graphnew

     

    图例描写叙述:

    首先第一步。我们有一张图Graph,有若干点和边 

     

    将全部的边的长度排序,用排序的结果作为我们选择边的根据。

    这里再次体现了贪心算法的思想。资源排序,对局部最优的资源进行选择,排序完毕后。我们领先选择了边AD。这样我们的图就变成了右图

     

     

     

    在剩下的变中寻找。我们找到了CE。

    这里边的权重也是5

    依次类推我们找到了6,7,7,即DF。AB,BE。

    以下继续选择, BC或者EF虽然如今长度为8的边是最小的未选择的边。可是如今他们已经连通了(对于BC能够通过CE,EB来连接,相似的EF能够通过EB,BA,AD,DF来接连)。所以不须要选择他们。

    相似的BD也已经连通了(这里上图的连通线用红色表示了)。

    最后就剩下EG和FG了。当然我们选择了EG。

    最后成功的图就是右:

     

     

     

    3.简单证明Kruskal算法

    对图的顶点数n做归纳,证明Kruskal算法对随意n阶图适用。

    归纳基础:

    n=1。显然能够找到最小生成树。

    归纳过程:

    如果Kruskal算法对n≤k阶图适用,那么,在k+1阶图G中。我们把最短边的两个端点a和b做一个合并操作,即把u与v合为一个点v'。把原来接在u和v的边都接到v'上去。这样就能够得到一个k阶图G'(u,v的合并是k+1少一条边),G'最小生成树T'能够用Kruskal算法得到。

    我们证明T'+{<u,v>}是G的最小生成树。

    用反证法,如果T'+{<u,v>}不是最小生成树,最小生成树是T。即W(T)<W(T'+{<u,v>})

    显然T应该包括<u,v>,否则,能够用<u,v>增加到T中,形成一个环,删除环上原有的随意一条边,形成一棵更小权值的生成树。而T-{<u,v>}。是G'的生成树。所以W(T-{<u,v>})<=W(T')。也就是W(T)<=W(T')+W(<u,v>)=W(T'+{<u,v>}),产生了矛盾。于是如果不成立。T'+{<u,v>}是G的最小生成树。Kruskal算法对k+1阶图也适用。

    由数学归纳法,Kruskal算法得证。

     

     

     

    4.代码算法实现

    复制代码
    typedef struct          
    {        
        char vertex[VertexNum];                                //顶点表         
        int edges[VertexNum][VertexNum];                       //邻接矩阵,可看做边表         
        int n,e;                                               //图中当前的顶点数和边数         
    }MGraph; 
     
    typedef struct node  
    {  
        int u;                                                 //边的起始顶点   
        int v;                                                 //边的终止顶点   
        int w;                                                 //边的权值   
    }Edge; 
    
    void kruskal(MGraph G)  
    {  
        int i,j,u1,v1,sn1,sn2,k;  
        int vset[VertexNum];                                    //辅助数组。判定两个顶点是否连通   
        int E[EdgeNum];                                         //存放全部的边   
        k=0;                                                    //E数组的下标从0開始   
        for (i=0;i<G.n;i++)  
        {  
            for (j=0;j<G.n;j++)  
            {  
                if (G.edges[i][j]!=0 && G.edges[i][j]!=INF)  
                {  
                    E[k].u=i;  
                    E[k].v=j;  
                    E[k].w=G.edges[i][j];  
                    k++;  
                }  
            }  
        }     
        heapsort(E,k,sizeof(E[0]));                            //堆排序,按权值从小到大排列       
        for (i=0;i<G.n;i++)                                    //初始化辅助数组   
        {  
            vset[i]=i;  
        }  
        k=1;                                                   //生成的边数,最后要刚好为总边数   
        j=0;                                                   //E中的下标   
        while (k<G.n)  
        {   
            sn1=vset[E[j].u];  
            sn2=vset[E[j].v];                                  //得到两顶点属于的集合编号   
            if (sn1!=sn2)                                      //不在同一集合编号内的话,把边增加最小生成树   
            {
                printf("%d ---> %d, %d",E[j].u,E[j].v,E[j].w);       
                k++;  
                for (i=0;i<G.n;i++)  
                {  
                    if (vset[i]==sn2)  
                    {  
                        vset[i]=sn1;  
                    }  
                }             
            }  
            j++;  
        }  
    }  
    复制代码


    时间复杂度:elog2e  e为图中的边数

  • 相关阅读:
    CREATE AGGREGATE
    技术文档列表
    jQuery 判断表单中多个 input text 中至少有一个不为空
    Java实现 蓝桥杯 算法提高 奥运会开幕式
    Java实现 蓝桥杯 算法提高 最长滑雪道
    Java实现 蓝桥杯 算法提高 最长滑雪道
    Java实现 蓝桥杯 算法提高 最长滑雪道
    Java实现 蓝桥杯 算法提高 最大值路径
    Java实现 蓝桥杯 算法提高 最大值路径
    Java实现 蓝桥杯 算法提高 最大值路径
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8687614.html
Copyright © 2020-2023  润新知