图——图的Prim法最小生成树实现

1,运营商的挑战:

图——图的Prim法最小生成树实现 

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

       2,要求:

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

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

      

2,问题抽象:

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

      

3,最小(大)生成树:

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

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

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

 

4,寻找最小生成树:

图——图的Prim法最小生成树实现 

      

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

图——图的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,最小生成树算法的原材料:

图——图的Prim法最小生成树实现 

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

      

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

图——图的Prim法最小生成树实现

 

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 算法的思想也能寻找图的“最大生成树”;

上一篇:(总结) 关于Dijkstra的一些看法


下一篇:(HW)Prim算法(Java)