zoukankan      html  css  js  c++  java
  • 图——图的Prim法最小生成树实现

    1,运营商的挑战:

     

           1,在下图标出的城市间架设一条通信线路;

           2,要求:

                  1,任意两个城市间都能够通信;

                  2,将架设成本降至最低;

          

    2,问题抽象:

           1,如何在图中选择 n - 1 条边使得 n 个顶点间两两可达,并且这 n - 1 条边的权值之和最小?

          

    3,最小(大)生成树:

           1,仅使用图中的 n - 1 条边连接图中的 n 个顶点;

           2,不能使用产生回路的边;

           3,各边上的权值总和达到最小(大);

    4,寻找最小生成树:

     

          

    5,使用 prim 方法手工寻找最小生成树:

     

          

    6,最小生成树算法步骤(prim):

           1,选择某一顶点 v0 作为起始顶点,使得 T = {v0},F = {v1, v2, ..., vn},E = {};

           2,每次选择一条边,这条边是所有(u, v)中权值最小的边,且 u 属于 T,v 属于 F;

           3,修改 T,F,E:

                  T = T + {v}, F = F - {v}, E = E + {(u, v)}

           4,当 F != NULL 时,且(u, v)存在,转 2;否则,结束;

          

    7,最小生成树算法的原材料:

     

           1,如果 T 集合到 F 集合中同一个顶点的连接有多条,那么选取权值最小的连接;

          

    8,最小生成树算法流程图:

     

    9,注意事项:

           1,最小生成树仅针对无向图有意义;

           2,必须判断图对象是否能够看做无向图;

                  1,如果可以才能够用 prim 算法;

                 

    10,有向图可看做无向图的充分条件:

           1,有向的任意两顶点之间若存在连接,则两顶点相互可达、权值相等;

          

    11,图类型(Graph)中的新增成员函数:

           1,virtual bool isAdjacent(int i, int j) = 0;

                  1,判断在当前图中顶点 i 到顶点 j 是否邻接;

           2,bool asUndirected();

                  1,判断当前的有向图是否能够看做无向图;

    12,最小生成树 prim 算法实现:

     1,判断邻接的实现:
      1,邻接矩阵的实现:
    1     /* 判断 i 到 j 顶点边是否连接,值不为空就连接 */
    2     bool isAdjacent(int i, int j)
    3     {
    4         return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_edges[i][j] != NULL);
    5     }
      2,邻接链表的实现:
    1     bool isAdjacent(int i, int j)
    2     {
    3         return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_list.get(i)->edge.find(Edge<E>(i, j)) >= 0);
    4     }

        2,判断是否为无向图的实现:

     1    /* 最小生成树只能用于无向图,而我们针对的是有向图,所以要判断有向图什么时候能够被当做无向图 */
     2     bool asUndirected()
     3     {
     4        bool ret = true;
     5 
     6        for(int i=0; i<vCount(); i++)
     7        {
     8            for(int j=0; j<vCount(); j++)
     9            {
    10                /* i 到 j 是连接的,并且 j 到 i 也是连接的,然后权值也要相等 */
    11                if( isAdjacent(i, j) )
    12                {
    13                    ret = ret && isAdjacent(j, i)  && (getEdge(i, j) == getEdge(j, i));
    14                }
    15            }
    16        }
    17 
    18        return ret;
    19     }

      3,Prim算法实现:

     1    /* Prim 法实现最小、大生成树;返回值是数组,因为最小、大生成树的结果就是一系列的边,所以返回边的数组;参数表示理论上的最大权值; */
     2     SharedPointer< Array< Edge<E> > > prim(const E& LIMIT, const bool MINIMUM = true)  // 返回一个指向存储边的数组指针
     3     {
     4         LinkQueue< Edge<E> > ret;  // 返回边队列,本质是 E 集合
     5 
     6         /* 执行 prim 算法 */
     7         if( asUndirected() )  // 无向图
     8         {
     9             DynamicArray<int> adjVex(vCount());//保存最小权值的边的 F集合中顶点
    10             DynamicArray<bool> mark(vCount());  // 保存 T 集合或者 F 集合的标记
    11             DynamicArray<E> cost(vCount());  // 保存最小权值的顶点中 E 集合中顶点,寻找最小值要配合 mark 使用
    12             SharedPointer< Array<int> > aj = NULL;  // 保存某个顶点邻接数组
    13             bool end = false;  // 用于标记判断 prim 是否要中断执行
    14             int v = 0;  // 代表习惯性的从 0 顶点生成最小生成树
    15 
    16             /* 执行初始化 */
    17             for(int i=0; i<vCount(); i++)
    18             {
    19                 adjVex[i] = -1;  // 没有边被访问
    20                 mark[i] = false;  // 顶点都没有被访问
    21                 cost[i] = LIMIT;  // 参数传递理论上的最大权值
    22             }
    23 
    24             mark[v] = true;  // 初始顶点做标记
    25 
    26             aj = getAdgacent(v);  // 获取初始顶点的邻接顶点
    27 
    28             /* 设置初始顶点对应的位置 */
    29             for(int j=0; j<aj->length(); j++)  
    30             {
    31                 cost[(*aj)[j]] = getEdge(v, (*aj)[j]);  // 保存/到对应顶点的响应权值
    32                 adjVex[(*aj)[j]] = v;  // 记录权值所对应的顶点,即能够得到边
    33             }
    34 
    35             /* 真正循环找边 */
    36             for(int i=0; (i<vCount()) && !end; i++)  // 最多循环顶点次;也可能条件不满足,提前结束,所以有 !end
    37             {
    38                 E m = LIMIT;
    39                 int k = -1;  // 记录最小值的顶点
    40 
    41                 /* 通过 cost 数组找最小值 */
    42                 for(int j=0; j<vCount(); j++)
    43                 {
    44                     if( !mark[j] && (MINIMUM ? (cost[j] < m) : (cost[j] > m)) ) // !makr[j] 条件是因为选取最小权值时本质上是选取连接的最小边,对应的顶点是在 F集合,此时 mark 中对应的值为假当中的,则在 mark 数组中对应的就为假,所以要这个条件,这里有最小值最大值的设置
    45           {
    46                         m = cost[j];
    47                         k = j;  // 得到记录的最小值的顶点号
    48                     }
    49                 }
    50                 end = (k == -1);  // 是否找到合法最小权值,因为有可能在上面 if 条件中没有找到合法的最小权值
    51                 if( !end )
    52                 {
    53                     ret.add(Edge<E>(adjVex[k], k, getEdge(adjVex[k], k)));  // 在 adjVex 中找到这条边
    54 
    55                     mark[k] = true;  // 标记顶点进入了 T 集合
    56 
    57                     aj = getAdgacent(k);  // 找新的集合连接
    58 
    59                     /* 找到之后更新 cost 数组和 adgVex 数组 */
    60                     for(int j=0; j<aj->length(); j++)
    61                     {
    62                         if( !mark[(*aj)[j]] && (MINIMUM ? (getEdge(k, (*aj)[j]) < cost[(*aj)[j]]) : (getEdge(k, (*aj)[j]) > cost[(*aj)[j]])) );  //只对 F 集合操作
    63                         {
    64                             cost[(*aj)[j]] = getEdge(k ,(*aj)[j]); //如果 T 到 F 集合新连接权值较小,则记录到 cost 数组中,新加入的点 k 和之前 T 集合里的点到 F 集合里的点的权值要比较呢;如果在 k 到 F 集合中找不到合适的点,则用T中的点代替
    65                             adjVex[(*aj)[j]] = k;  // 将最小权值的起始点设入到邻接边中
    66                         }
    67                     }
    68                 }
    69             }
    70         }
    71         else
    72         {
    73             THROW_EXCEPTION(InvalidOperationException, "Prim operation is for undirected graph only ...");
    74         }
    75 
    76         /* 判断边的数目是否够,即 n-1 条边 */
    77         if( ret.length() != (vCount() - 1) )
    78         {
    79             THROW_EXCEPTION(InvalidOperationException, "No enough edge for prim operation ...");
    80         }
    81         return toArray(ret);  // 返回值是边的数组
    82     }

    14,prim 算法测试代码:

     1 #include <iostream>
     2 #include "MatrixGraph.h"
     3 #include "ListGraph.h"
     4 
     5 using namespace std;
     6 using namespace DTLib;
     7 
     8 template< typename V, typename E >
     9 Graph<V, E>& GraphEasy()
    10 {
    11    static MatrixGraph<4, V, E> g;
    12 
    13     g.setEdge(0, 1, 1);
    14     g.setEdge(1, 0, 1);
    15     g.setEdge(0, 2, 3);
    16     g.setEdge(2, 0, 3);
    17     g.setEdge(1, 2, 1);
    18     g.setEdge(2, 1, 1);
    19     g.setEdge(1, 3, 4);
    20     g.setEdge(3, 1, 4);
    21     g.setEdge(2, 3, 1);
    22    g.setEdge(3, 2, 1);
    23 
    24     return g;
    25 }
    26 
    27 template< typename V, typename E >
    28 Graph<V, E>& GraphComplex()
    29 {
    30    static ListGraph<V, E> g(9);
    31 
    32     g.setEdge(0, 1, 10);
    33     g.setEdge(1, 0, 10);
    34     g.setEdge(0, 5, 11);
    35     g.setEdge(5, 0, 11);
    36     g.setEdge(1, 2, 18);
    37     g.setEdge(2, 1, 18);
    38     g.setEdge(1, 8, 12);
    39     g.setEdge(8, 1, 12);
    40     g.setEdge(1, 6, 16);
    41     g.setEdge(6, 1, 16);
    42     g.setEdge(2, 3, 22);
    43     g.setEdge(3, 2, 22);
    44     g.setEdge(2, 8, 8);
    45     g.setEdge(8, 2, 8);
    46     g.setEdge(3, 8, 21);
    47     g.setEdge(8, 3, 21);
    48     g.setEdge(3, 6, 24);
    49     g.setEdge(6, 3, 24);
    50     g.setEdge(3, 7, 16);
    51     g.setEdge(7, 3, 16);
    52     g.setEdge(3, 4, 20);
    53     g.setEdge(4, 3, 20);
    54     g.setEdge(4, 5, 26);
    55     g.setEdge(5, 4, 26);
    56     g.setEdge(4, 7, 7);
    57     g.setEdge(7, 4, 7);
    58     g.setEdge(5, 6, 17);
    59     g.setEdge(6, 5, 17);
    60     g.setEdge(6, 7, 19);
    61    g.setEdge(7, 6, 19);
    62 
    63     return g;
    64 }
    65 
    66 int main()
    67 {
    68     Graph<int, int>& g = GraphEasy<int, int>();
    69    SharedPointer< Array< Edge<int> > > sa = g.prim(65535);  
    70 
    71    int w = 0;
    72 
    73     for(int i=0; i<sa->length(); i++)
    74     {
    75         w += (*sa)[i].data;
    76 
    77         cout << (*sa)[i].b << " " << (*sa)[i].e << " " << (*sa)[i].data << endl;
    78    }
    79 
    80    cout << "Weight: " << w << endl;
    81 
    82     return 0;
    83 }

                 

    15,小结:

           1,最小生成树使得顶点间的连通代价最小;

           2,Prim 算法通过顶点的动态标记寻找最小生成树;

           3,Prim 算法的关键是集合概念的运用(T 集合,F 集合);

           4,利用 Prim 算法的思想也能寻找图的“最大生成树”;

  • 相关阅读:
    ES(一): 架构及原理
    Durid(二): 数据集及存储
    Durid(一): 原理架构
    【DataBase】H2 DateBase与项目集成
    【DataBase】H2 DateBase的拓展使用
    【Mybatis】MyBatis之插件开发(十)
    【DataBase】H2 DateBase的简单使用
    【DataBase】Hsqldb与项目集成
    【DataBase】Hsqldb的简单使用
    【Mybatis】MyBatis之Generator自动生成代码(九)
  • 原文地址:https://www.cnblogs.com/dishengAndziyu/p/10926547.html
Copyright © 2011-2022 走看看