最小生成树

1. 无向图与开放树

  在讲最小生成树之前,先来说一说开放树。连通而无环路的无向图称做开放树。如果指定开放树中某一顶点为根,并且把每一条边看成是背离根的,则一棵开放树变成一棵树。

开放树有2个性质:

  • 具有n>=1个顶点的开放树包含n-1条边
  • 如果在开放树中任意增加一条边,将构成一个环路

如下列两个图:

image_1be1se15l19kkj7f15k6plugod9.png-7.8kB

2. 最小生成树

只针对无向图

  假设E中的每条边都有权重,为c(u,v),也叫做边长。图G的一棵生成树是连接V中所有顶点的一棵开放树。将生成树中所有边长的总和称为生成树的价。使这个价最小的生成树称为图的最小生成树

  设G=(V,E)是一个连通图,在E上定义一个权函数C,且{(V1,T1),(V2,T2),...,(Vk,Tk)}是G的任意生成森林,令

    image_1be1srah117bj110t91lc8v17q7m.png-2.5kB

  其中,|T| <= |E|,又假设e=(v,w)是E-T中的一条边,其权值C[v][w]最小,而且v在V1中,w不在V1中,则图G有一棵包含T和e的生成树,其价不大于包含T的任何生成树的价。

以上的性质下,两种算法应运而生:Prim算法和Kruskal算法

  • Prim算法
    /*输入为加权无向图G=(V,E),其中V={1,2,3,...,n}
     要点:引进集合U和T,U准备放顶点,T准备放边。初值为U={1},T为空,选择最小权的边(u,v),使u在U中,v在V-U中,将v加入U,(u,v)加入T。重复这个过程 */
     
     
     void Prim(costtype C[n+1][n+1])
     {
        /*CLOSECT表示U中的顶点,(i,CLOSECT[i])具有最小的权,而LOWCOST[i]表示该边的权,其中i不在U中  */
        
        costtype LOWCOST[n+1];
        int CLOSEST[n+1];
        int i,j,k;
        costtype min;
        for(i=2;i <= n;i++)
        {
            //初始化
            LOWCOST[i] = C[1][i];
            CLOSEST[i] = 1;
        }
        
        for(i=2;i=n;i++)
        {
            min = LOWCOST[i];
            k=i;
            //找出最小权的边
            for(j=2;j<=n;j++) {
                if(LOWCOST[j] < min) {
                    min = LOWCOST[j];
                    k = j;
                }
            cout<<"("<<k<<","<<CLOSECT[k] << ")"<<endl;
            LOWCOST[k] = infinity;  /* k加入U */
            
            
            //对新加入的k的基础上,更新LOWCOST和CLOSECT
            for(j = 2;j<=n;j++) {
                if(C[k][j] < LOWCOST[j] && LOWCOST[j] != infinity) {
                    LOWCOST[j] = C[k][j];
                    CLOSECT[j] = k;
                }
            }
        }
     }
  • Kruskal算法
    void Kruskal(V,T)
    {
        T = V;
        ncomp = n;
        while(ncomp > 1) {
            从E中取出并删除权最小的边(v,u)
            if(v和u属于T中不同的连通分量) {
                T= T + (v,u);
                ncomp --;
            }
        }
    }

Prim算法复杂度为O(n2),而Kruskal算法复杂度为O(ne),故Prim算法适用于点较少的情况

3. 最小树形图

只针对有向图

  首先为除根之外的每个点选定一条入边,这条入边一定要是所有入边中最小的。现在所有的最小入边都选择出来了,如果这个入边集不存在有向环的话,我们可以证明这个集合就是该图的最小树形图。这个证明并不是很难。如果存在有向环的话,我们就要将这个有向环缩成一个人工顶点,同时改变图中边的权。假设某点u在该环上,并设这个环中指向u的边权是in[u],那么对于每条从u出发的边(u, i, w),w为权,在新图中连接(new, i, w)的边,其中new为新加的人工顶点; 对于每条进入u的边(i, u, w),在新图中建立边(i, new, w-in[u])的边。

image_1be20ei6d1dh91r02rb6r9fa5p13.png-250.9kB

    int ZLEdmonds(int n,int map[maxn][maxn])  
    {  
        bool visited[maxn],flag[[maxn];  
        int pre[maxn];  
        int sum=0,i,j,k;  
        for( i=0; i<n; i++){  
             flag[i]=false;  
             map[i][i]=INF;  
        }  
        pre[0]=0;  
        while( true){  
               //求最短弧集合E0。  
               for( i=1; i<n; i++){  
                    if( flag[i]) continue;  
                    pre[i]=i;  
                    for( j=0; j<n; j++){   //pre[i]保存终点为i的最短弧的起点。  
                         if( !flag[j]&&map[j][i]<map[pre[i]][i])  
                             pre[i]=j;      
                    }  
                    if( pre[i]==i) return -1;  
               }   
               //检查E0  
               for( i=1; i<n; i++){  
                    if( flag[i]) continue;  
                    for( j=0; j<n; j++)  
                         visited[j]=false;  
                    visited[0]=true;  
                      
                    j=i;  
                    do{  
                        visited[j]=true;  
                        j=pre[j];  
                    }while( !visited[j]);   
                    if( !j) continue; //没有找到环。  
                       
                    i=j;//将整个环的权值保存,累计入原图的最小树形图  
                    do{  
                        sum+=map[pre[j]][j];  
                        j=pre[j];  
                    }while( j!=i);  
                      
                    j=i;//对于环上的点有关的边,修改边权  
                    do{  
                        for( k=0; k<n; k++){  
                             if( !flag[k]&&map[k][j]&&map[k][j]<INF&&k!=pre[j])  
                                 map[k][j]-=map[pre[j]][j];  
                        }  
                        j=pre[j];  
                    }while( j!=i);  
                      
                    //缩点,将整个环缩成i号点,所有环上的点有关的边转移到点i  
                    for( j=0; j<n; j++){  
                         if( j==i) continue;  
                         for( k=pre[i]; k!=i; k++){  
                                
                              if( map[k][j]<map[i][j])  
                                  map[i][j]=map[k][j];  
                              if( map[j][k]<map[j][i])  
                                  map[j][i]=map[j][k];   
                         }  
                    }  
                    //标记环上其他的点为被缩掉  下次再找Ei时不参与   
                    for( j=pre[i]; j!=i; j=pre[j])  
                         falg[j]=true;  
                    //当前环缩点结束,形成新的图G',跳出继续求G'的最小树形图 ,累计入sum。   
               }   
               if( i==n){  
                   for( i=0; i<n; i++)  
                        if( !flag[i])  
                            sum+=map[pre[i]][i];  
                   break;  
               }  
        }          
             return sum;          
    }  
原文地址:https://www.cnblogs.com/vachester/p/6732054.html