• 最小生成树算法(Prim,Kruskal)


    边赋以权值的图称为网或带权图带权图的生成树也是带权的生成树T各边的权值总和称为该树的权。

       最小生成树(MST):权值最小的生成树。

       生成树和最小生成树的应用:要连通n个城市需要n-1条边线路。可以把边上的权值解释为线路的造价。则最小生成树表示使其造价最小的生成树。

       构造网的最小生成树必须解决下面两个问题:

        1、尽可能选取权值小的边,但不能构成回路

        2、选取n-1条恰当的边以连通n个顶点

        MST性质:假设G=(V,E)是一个连通网,U是顶点V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。

    1. prim算法

    基本思想:假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:

       在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。

       此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。

       Prim算法的核心:始终保持TE中的边集构成一棵生成树。

    注意:prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(eloge)跟边的数目有关,适合稀疏图。

    看了上面一大段文字是不是感觉有点晕啊,为了更好理解我在这里举一个例子,示例如下:

     

    (1)图中有6个顶点v1-v6,每条边的边权值都在图上;在进行prim算法时,我先随意选择一个顶点作为起始点,当然我们一般选择v1作为起始点,好,现在我们设U集合为当前所找到最小生成树里面的顶点,TE集合为所找到的边,现在状态如下:

    U={v1}; TE={};

    (2)现在查找一个顶点在U集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值。

     

    通过图中我们可以看到边v1-v3的权值最小为1,那么将v3加入到U集合,(v1,v3)加入到TE,状态如下:

    U={v1,v3}; TE={(v1,v3)};

    (3)继续寻找,现在状态为U={v1,v3}; TE={(v1,v3)};在与红线相交的边上查找最小值。

    我们可以找到最小的权值为(v3,v6)=4,那么我们将v6加入到U集合,并将最小边加入到TE集合,那么加入后状态如下:

    U={v1,v3,v6}; TE={(v1,v3),(v3,v6)}; 如此循环一下直到找到所有顶点为止。

    (4)下图像我们展示了全部的查找过程:

    2.prim算法程序设计

    (1)由于最小生成树包含每个顶点,那么顶点的选中与否就可以直接用一个数组来标记used[max_vertexes];(我们这里直接使用程序代码中的变量定义,这样也易于理解);当选中一个数组的时候那么就标记,现在就有一个问题,怎么来选择最小权值边,注意这里最小权值边是有限制的,边的一个顶点一定在已选顶点中,另一个顶点当然就是在未选顶点集合中了。我最初的一个想法就是穷搜了,就是在一个集合中选择一个顶点,来查找到另一个集合中的最小值,这样虽然很易于理解,但是很明显效率不是很高,在严蔚敏的《数据结构》上提供了一种比较好的方法来解决:设置两个辅助数组lowcost[max_vertexes]和closeset[max_vertexes],lowcost[max_vertexes]数组记录从U到V-U具有最小代价的边。对于每个顶点v∈V-U,closedge[v], closeset[max_vertexes]记录了该边依附的在U中的顶点。

    注意:我们在考虑两个顶点无关联的时候设为一个infinity 1000000最大值。

    说了这么多,感觉有点罗嗦,还是发扬原来的风格举一个例子来说明,示例如下:

    过程如下表:顶点标号都比图中的小1,比如v10v21这里首先选择v1

     

     

    Lowcost[0]

    Lowcost[1]

    Lowcost[2]

    Lowcost[3]

    Lowcost[4]

    Lowcost[5]

    U

    V-U

    closeset

    v1,infinity

    v1,6

    v1,1

    v1,5

    v1,infinity

    v1,infinity

    v1

    v1,v2,v3,v4,v5,v6

     

     

    从这个表格可以看到依附到v1顶点的v3Lowcost最小为1,那么选择v3,选择了之后我们必须要更新Lowcost数组的值,因为记录从U到V-U具有最小代价的边,加入之后就会改变。这里更新Lowcost和更新closeset数组可能有点难理解,

     for (k=1;k<vcount;k++)
                if (!used[k]&&(G[j][k]<lowcost[k]))
                    { lowcost[k]=G[j][k];
                    closeset[k]=j; }
            }

     j为我们已经选出来的顶点,如果G[j][k]<lowcost[k],则意味着最小权值边发生变化,更新该顶点的最小lowcost权值,依附的顶点肯定就是刚刚选出的顶点j,closeset[k]=j。

     

     

    Lowcost[0]

    Lowcost[1]

    Lowcost[2]

    Lowcost[3]

    Lowcost[4]

    Lowcost[5]

    U

    V-U

    closeset

    v1,infinity

    v1,6

    v1,1

    v1,5

    v3,6

    v3,4

    v1v3

    v1,v2,v4,v5,v6

     

     

     

     

    这样一直选择下去直到选出所有的顶点。

    (2)上面把查找最小权值的边结束了,但是这里有一个问题,就是我们没有存储找到的边,如果要求你输出找到的边那么这个程序就需要改进了,我们刚开始的时候选取的是v1作为第一个选择的顶点,那我们设置一个father[]数组来记录每个节点的父节点,当然v1的父节点肯定没有,那么我们设置一个结束标志为-1,每次找到一个新的节点就将它的父节点设置为他依附的节点,这样就可以准确的记录边得存储了。

     

    语法:prim(Graph G,int vcount,int father[]);

    参数:

    G: 图,用邻接矩阵表示

    vcount:表示图的顶点个数

    father[]:用来记录每个节点的父节点

    返回值:null

    注意:

      常数max_vertexes为图最大节点数

      常数infinity为无穷大 

      数组存储从0开始

      如果下面的源程序有错请参照测试程序。

     

    #define infinity 1000000
    #define max_vertexes 5 
    
    typedef int Graph[max_vertexes][max_vertexes];
    
    void prim(Graph G,int vcount,int father[])
    {
        int i,j,k;
        int lowcost[max_vertexes];
       int closeset[max_vertexes],used[max_vertexes];
        int min;
        for (i=0;i<vcount;i++)
        {
               /* 最短距离初始化为其他节点到1号节点的距离 */
              lowcost[i]=G[0][i];
            /* 标记所有节点的依附点皆为默认的1号节点 */
    
              closeset[i]=0; 
              used[i]=0;
              father[i]=-1; 
         }
        used[0]=1;  /*第一个节点是在U集合里的*/
      /* vcount个节点至少需要vcount-1条边构成最小生成树 */
        for (i=1;i<=vcount-1;i++)
        {
            j=0;
          min = infinity;
           /* 找满足条件的最小权值边的节点k */
            for (k=1;k<vcount;k++)
             /* 边权值较小且不在生成树中 */
                if ((!used[k])&&(lowcost[k]<min)) 
              {
                  min =  lowcost[k];
                  j=k;
               }
            father[j]=closeset[j]; 
            used[j]=1;;//把第j个顶点并入了U中
            for (k=1;k<vcount;k++)
             /* 发现更小的权值 */
                if (!used[k]&&(G[j][k]<lowcost[k]))
                    { 
                      lowcost[k]=G[j][k];/*更新最小权值*/
                      closeset[k]=j;;/*记录新的依附点*/
                     }
         }
    }

    测试程序:

     

    测试用例:

    1 2 6

    1 3 1

    1 4 5

    2 3 5

    2 5 3

    3 4 5

    3 5 6

    3 6 4

    5 6 6

    4 6 2

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define infinity 1000000
    #define max_vertexes 6 
    typedef int Graph[max_vertexes][max_vertexes];
    void prim(Graph G,int vcount,int father[])
    {    
      int i,j,k; 
       int lowcost[max_vertexes];
      int closeset[max_vertexes],used[max_vertexes];
      int min;  
      for (i=0;i<vcount;i++)     
      {
    /* 最短距离初始化为其他节点到1号节点的距离 */   
        lowcost[i]=G[0][i];
        /* 标记所有节点的依附点皆为默认的1号节点 */
         closeset[i]=0;      
        used[i]=0;    
        father[i]=-1;      
      }    
      used[0]=1; /*第一个节点是在s集合里的*/
    /* vcount个节点至少需要vcount-1条边构成最小生成树 */  
      for (i=1;i<=vcount-1;i++)      
       {       
         j=0;
           min = infinity;
           /* 找满足条件的最小权值边的节点k */      
         for (k=1;k<vcount;k++)
             /* 边权值较小且不在生成树中 */     
         if ((!used[k])&&(lowcost[k]<min)) 
          {  
                  min =  lowcost[k];
                  j=k;
            }       
        father[j]=closeset[j];   
       printf("%d %d
    ",j+1,closeset[j]+1);//打印边   
       used[j]=1;;//把第j个顶点并入了U中     
       for (k=1;k<vcount;k++)
             /* 发现更小的权值 */       
         if (!used[k]&&(G[j][k]<lowcost[k]))       
        { 
                      lowcost[k]=G[j][k];/*更新最小权值*/       
                  closeset[k]=j;;/*记录新的依附点*/
          }      
       }
    }
                     
    int main()
    {
      FILE *fr;
      int i,j,weight;
      Graph G;
      int fatheer[max_vertexes];
      for(i=0; i<max_vertexes; i++)
      for(j=0; j<max_vertexes; j++)
      G[i][j] = infinity;
      fr = fopen("prim.txt","r");
      if(!fr)
      {
        printf("fopen failed
    ");
        exit(1); 
      }
      while(fscanf(fr,"%d%d%d", &i, &j, &weight) != EOF)
      {  
        G[i-1][j-1] = weight;
        G[j-1][i-1] = weight;
      }
    
       prim(G,max_vertexes,fatheer);
       return 0;
    
    }

    程序结果:

    3 1

    6 3

    4 6

    2 3

    5 2

    请按任意键继续. . .

     

     

    克鲁斯卡尔算法(Kruskal's algorithm)是两个经典的最小生成树算法的较为简单理解的一个。这里面充分体现了贪心算法的精髓。大致的流程可以用一个图来表示。这里的图的选择借用了Wikipedia上的那个。非常清晰且直观。

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

    如下图所示:

    graph

     

    第一步我们要做的事情就是将所有的边的长度排序,用排序的结果作为我们选择边的依据。这里再次体现了贪心算法的思想。资源排序,对局部最优的资源进行选择。

    排序完成后,我们率先选择了边AD。 这样我们的图就变成了

    图1

    第二步,在剩下的变中寻找。我们找到了CE。这里边的权重也是5

     

    图二

    依次类推我们找到了6,7,7。完成之后,图变成了这个样子。

     

    图三

     

    下一步就是关键了。下面选择那条边呢? BC或者EF吗?都不是,尽管现在长度为8的边是最小的未选择的边。但是现在他们已经连通了(对于BC可以通过CE,EB来连接,类似的EF可以通过EB, BA, AD, DF来接连)。所以我们不需要选择他们。类似的BD也已经连通了(这里的连通线用红色表示了)。所以最后就剩下EG和FG了。当然我们选择了EG。 最后成功的图就是下图:

     

    图四

    到这里所有的边点都已经连通了,一个最小生成树构建完成。

    如果要简要得描述这个算法的话就是,首先边的权重排序。(从小到大)循环的判断是否需要选择这里的边。判断的依据则是边的两个顶点是否已经连通,如果连通则继续下一条。不连通就选择使其连通。这个流程还是非常清晰明了。

    但是在实现的时候,困难的地方在于如何描述2个点已然连通? 这里用到了并查集做辅助,至于并查集可以到这里去看看。

    这里贴出并查集的代码和Kruscal的C++实现:

    /*
    *
    *  Disjoint_Set_Forest.h -- an implementation for disjoint set data structure
    *
    *  Created by Ge Chunyuan on 04/09/2009.
    *
    *  version: 0.1
    */
    #pragma once
    #ifndef _DISJOINT_SET_H_
    #define _DISJOINT_SET_H_
    #include <vector>
    template <typename T> class DisjointSet
    {
    public:
        DisjointSet();
        ~DisjointSet();
        void    makeSet        ( const std::vector<T>& s );
        bool    findSet        ( const T& s, T& parent); 
        void    Union        ( const T& s1, const T& s2 );
    protected:
        struct Node
        {
            int        rank;
            T        data;
            Node*    parent; 
        };
        int m_nElementCnt; 
        int m_nSetCnt; 
        std::vector<Node*> m_Nodes; 
    };
    template< class T> DisjointSet<T>::DisjointSet()
    {
        m_nElementCnt = 0;    
        m_nSetCnt = 0;        
    }
    template< class T> DisjointSet<T>::~DisjointSet()
    {
        for (int i=0;i<m_nElementCnt;i++)
            delete m_Nodes[i];
    }
    template< class T> void DisjointSet<T>::makeSet( const std::vector<T>& s )
    {
        m_nElementCnt += (int)s.size();
        m_nSetCnt += (int)s.size();
        std::vector<T>::const_iterator it = s.begin();
        for (;it != s.end(); ++ it)
        {
            Node* pNode = new Node;
            pNode->data = *it;
            pNode->parent = NULL;
            pNode->rank = 0;
            m_Nodes.push_back(pNode);
        }
    }
    template< class T> bool DisjointSet<T>::findSet( const T& s, T& parent)
    {
        
        Node* curNode = NULL;
        bool find =false;    
        for (int i=0;i<(int)m_Nodes.size();i++)
        {
            curNode = m_Nodes[i];
            if (curNode->data == s)
            {
                find = true;
                break;
            }
        }
        
        if (!find) return false;
        
        // find the root 
        Node* pRoot = curNode;
        while (pRoot->parent != NULL)
        {
            pRoot = pRoot->parent;
        }
        
        // update all curNode's parent to root
        while (curNode != pRoot)
        {
            Node* pNext = curNode->parent;
            curNode->parent = pRoot;
            curNode = pNext;
        }
        parent = pRoot->data;
        return true;
    }
    template< class T> void    DisjointSet<T>::Union( const T& s1, const T& s2 )
    {
        Node* pNode1 = NULL;
        Node* pNode2 = NULL;
        int find = 0;
        for (int i=0;i<(int)m_Nodes.size();++i)
        {
            if (m_Nodes[i]->data == s1 || m_Nodes[i]->data == s2 )
            {
                find ++;
                if (m_Nodes[i]->data == s1)
                    pNode1 = m_Nodes[i];
                else
                    pNode2 = m_Nodes[i];
            }
        }
        // not found 
        if ( find != 2) return ;
            
        if (pNode1->rank > pNode2->rank)
            pNode2->parent = pNode1;
        else if (pNode1->rank < pNode2->rank)
            pNode1->parent = pNode2;
        else
        {
            pNode2->parent = pNode1;
            ++ pNode1->rank;
        }
        --m_nSetCnt;
    }
    #endif //_DISJOINT_SET_H_
    // Kruscal_Algorithm.cpp : Defines the entry point for the console application.
    //
    #include "stdafx.h"
    #include <string>
    #include <vector>
    #include <algorithm>
    #include <iostream>
    #include "Disjoint_Set_Forest.h"
    struct Vertex 
    {
        Vertex () {    }
        Vertex (std::string n) 
        {
            name = n;
        }
        
        bool operator==(const Vertex& rhs)
        {
            return name == rhs.name;
        }
        bool operator!=(const Vertex& rhs)
        {
            return name != rhs.name;
        }
        std::string name;
    };
    struct Edge 
    {
        Edge () {}
        Edge (Vertex v1, Vertex v2, int w)
        {
            this->v1 = v1;
            this->v2 = v2;
            this->w = w;
        }
        
        Vertex v1;
        Vertex v2;
        int w;
    };
    struct EdgeSort
    {
        bool operator()(const Edge& e1, const Edge& e2)
        {
            return e1.w<e2.w;
        }
    };
    struct PrintEdge
    {
        void operator() (Edge e) 
        {
            std::cout<< "edge start from "<<e.v1.name <<" to "<<e.v2.name << " with length = "<<e.w <<std::endl;;
        }
    };
    class Graph
    {
    public:
        void appendVertex ( const Vertex& v1)
        {
            m_vertexs.push_back(v1);
        }
            
        void appendEdge   ( const Vertex& v1, const Vertex& v2, int w)
        {
            m_edges.push_back( Edge(v1,v2,w) );
        }
        void minimumSpanningKruskal ()
        {
            std::vector<Edge> result;
            std::sort (m_edges.begin(), m_edges.end(), EdgeSort());        
            
            DisjointSet<Vertex> dv;
            dv.makeSet(m_vertexs);
            std::vector<Edge>::iterator it = m_edges.begin();
            for (;it!= m_edges.end();++it)
            {
                Vertex p1;
                Vertex p2;
                bool b1 = dv.findSet(it->v1, p1 );
                bool b2 = dv.findSet(it->v2, p2 );
                if ( b1&& b2 && (p1 != p2))
                {
                    dv.Union(p1, p2);
                    result.push_back(*it);
                }
            }
            for_each(result.begin(), result.end(), PrintEdge());
        }
    protected:
        std::vector<Vertex> m_vertexs;
        std::vector<Edge>   m_edges;
    };
    int _tmain(int argc, _TCHAR* argv[])
    {
        Graph gr;
        Vertex a("A");
        Vertex b("B");
        Vertex c("C");
        Vertex d("D");
        Vertex e("E");
        Vertex f("F");
        Vertex g("G");
            
        gr.appendVertex(a);
        gr.appendVertex(b);
        gr.appendVertex(c);
        gr.appendVertex(d);
        gr.appendVertex(e);
        gr.appendVertex(f);
        gr.appendVertex(g);
        
        gr.appendEdge(a,b,7);
        gr.appendEdge(a,d,5);
        gr.appendEdge(b,c,8);
        gr.appendEdge(b,d,9);
        gr.appendEdge(b,e,7);
        gr.appendEdge(c,e,5);
        gr.appendEdge(d,e,15);
        gr.appendEdge(d,f,6);
        gr.appendEdge(e,f,8);
        gr.appendEdge(e,g,9);
        gr.appendEdge(f,g,11);
        gr.minimumSpanningKruskal();
        system("pause");
        return 0;
    }

     

  • 相关阅读:
    Docker系列【Docker中启动flowable查看流转日志时字体显示方框问题】
    常用功能系列【hutool生成验证码】
    注册表立即生效命令
    DataX Kettle 使用场景的对比
    图片查看器.reg
    mysql error :1114 The table ‘XXX‘ is full
    常用用时
    MySQL 之 SQL Modes
    生产环境迁移至预发布环境
    MySQL:[Err] 1292 Incorrect datetime value: ‘00000000 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
  • 原文地址:https://www.cnblogs.com/scarecrow-blog/p/3604662.html
Copyright © 2020-2023  润新知