最小生成树算法(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;
}

 

原文地址:https://www.cnblogs.com/scarecrow-blog/p/3604662.html