圖——圖的Prim法最小生成樹實現

1,運營商的挑戰:ios

 

       1,在下圖標出的城市間架設一條通訊線路;算法

       2,要求:數組

              1,任意兩個城市間都可以通訊;函數

              2,將架設成本降至最低;測試

      

2,問題抽象:spa

       1,如何在圖中選擇 n - 1 條邊使得 n 個頂點間兩兩可達,而且這 n - 1 條邊的權值之和最小?指針

      

3,最小(大)生成樹:code

       1,僅使用圖中的 n - 1 條邊鏈接圖中的 n 個頂點;對象

       2,不能使用產生迴路的邊;blog

       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 算法的思想也能尋找圖的「最大生成樹」;

相關文章
相關標籤/搜索