最小生成樹


一、定義

  • 連通圖:在無向圖中,若任意兩個頂點vi與vj都有路徑相通,則稱該無向圖為連通圖。
  • 強連通圖:在有向圖中,若任意兩個頂點vi與vj都有路徑相通,則稱該有向圖為強連通圖。
  • 連通圖:在連通圖中,若圖的邊具有一定的意義,每一條變都有對應着一個數,稱為權,權代表着連接兩個頂點的代價,稱這種連通圖叫做連通網。
  • 生成樹:一個連通圖的生成樹是指一個連通子圖,它含有圖中全部n個頂點,但只有足以構成一顆樹的n-1條邊。一顆有n個頂點的生成樹有且僅有n-1條邊,如果生成樹中再添加一條邊,則必定成環。
  • 最小生成樹:(代價最小)一個有n個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有n個結點,並且有保持圖連通的最少的邊。

最小生成樹可以用kruskal(克魯斯卡爾)算法或prim(普里姆)算法求出。

 

貪心算法:

每一步都要最好的,權重最小的邊。

需要的約束:

  • 智能用圖里有的邊
  • 智能正好用掉|v|-1條邊
  • 不能有回路

 

二、Kruskal算法

此算法可以稱為“加邊法”,初始最小生成樹邊數為0,每迭代一次就選擇一條滿足條件的最小代價邊,加入到最小生成樹的邊集合里。

1.把圖中的所有邊按代價從小到打排序;

2.把圖中的n個頂點看成獨立的n棵樹組成的森林;

3.按權值從小到大選擇邊,所選的邊連接的兩個頂點ui,vi,應屬於兩顆不同的樹,則成為最小生成樹的一條邊,並將這兩顆樹合並作為一棵樹。

4.重復(3),直到所有頂點都在一棵樹內或者有n-1條邊為止。

 1 void Kruskal(Graph G)
 2 {
 3     MST = {};
 4     while(MST中不到|V|-1條邊&&E中還有邊) {
 5         從E中取一條權重最小的邊E(V,W);
 6         將E(V,W)從E中刪除;
 7         if(E(V,W)不在MST中構成回路)
 8             將E(V,W)加入MST;
 9         else
10             徹底無視E(V,W);
11     }
12     if(MST中不到|V|-1條邊)
13         Error("生成樹不存在");
14 }
Kruskal邏輯

T = O(|E|log|E|)

  1 /* 鄰接表存儲 - Kruskal最小生成樹算法 */
  2  
  3 /*-------------------- 頂點並查集定義 --------------------*/
  4 typedef Vertex ElementType; /* 默認元素可以用非負整數表示 */
  5 typedef Vertex SetName;     /* 默認用根結點的下標作為集合名稱 */
  6 typedef ElementType SetType[MaxVertexNum]; /* 假設集合元素下標從0開始 */
  7  
  8 void InitializeVSet( SetType S, int N )
  9 { /* 初始化並查集 */
 10     ElementType X;
 11  
 12     for ( X=0; X<N; X++ ) S[X] = -1;
 13 }
 14  
 15 void Union( SetType S, SetName Root1, SetName Root2 )
 16 { /* 這里默認Root1和Root2是不同集合的根結點 */
 17     /* 保證小集合並入大集合 */
 18     if ( S[Root2] < S[Root1] ) { /* 如果集合2比較大 */
 19         S[Root2] += S[Root1];     /* 集合1並入集合2  */
 20         S[Root1] = Root2;
 21     }
 22     else {                         /* 如果集合1比較大 */
 23         S[Root1] += S[Root2];     /* 集合2並入集合1  */
 24         S[Root2] = Root1;
 25     }
 26 }
 27  
 28 SetName Find( SetType S, ElementType X )
 29 { /* 默認集合元素全部初始化為-1 */
 30     if ( S[X] < 0 ) /* 找到集合的根 */
 31         return X;
 32     else
 33         return S[X] = Find( S, S[X] ); /* 路徑壓縮 */
 34 }
 35  
 36 bool CheckCycle( SetType VSet, Vertex V1, Vertex V2 )
 37 { /* 檢查連接V1和V2的邊是否在現有的最小生成樹子集中構成回路 */
 38     Vertex Root1, Root2;
 39  
 40     Root1 = Find( VSet, V1 ); /* 得到V1所屬的連通集名稱 */
 41     Root2 = Find( VSet, V2 ); /* 得到V2所屬的連通集名稱 */
 42  
 43     if( Root1==Root2 ) /* 若V1和V2已經連通,則該邊不能要 */
 44         return false;
 45     else { /* 否則該邊可以被收集,同時將V1和V2並入同一連通集 */
 46         Union( VSet, Root1, Root2 );
 47         return true;
 48     }
 49 }
 50 /*-------------------- 並查集定義結束 --------------------*/
 51  
 52 /*-------------------- 邊的最小堆定義 --------------------*/
 53 void PercDown( Edge ESet, int p, int N )
 54 { /* 改編代碼4.24的PercDown( MaxHeap H, int p )    */
 55   /* 將N個元素的邊數組中以ESet[p]為根的子堆調整為關於Weight的最小堆 */
 56     int Parent, Child;
 57     struct ENode X;
 58  
 59     X = ESet[p]; /* 取出根結點存放的值 */
 60     for( Parent=p; (Parent*2+1)<N; Parent=Child ) {
 61         Child = Parent * 2 + 1;
 62         if( (Child!=N-1) && (ESet[Child].Weight>ESet[Child+1].Weight) )
 63             Child++;  /* Child指向左右子結點的較小者 */
 64         if( X.Weight <= ESet[Child].Weight ) break; /* 找到了合適位置 */
 65         else  /* 下濾X */
 66             ESet[Parent] = ESet[Child];
 67     }
 68     ESet[Parent] = X;
 69 }
 70  
 71 void InitializeESet( LGraph Graph, Edge ESet )
 72 { /* 將圖的邊存入數組ESet,並且初始化為最小堆 */
 73     Vertex V;
 74     PtrToAdjVNode W;
 75     int ECount;
 76  
 77     /* 將圖的邊存入數組ESet */
 78     ECount = 0;
 79     for ( V=0; V<Graph->Nv; V++ )
 80         for ( W=Graph->G[V].FirstEdge; W; W=W->Next )
 81             if ( V < W->AdjV ) { /* 避免重復錄入無向圖的邊,只收V1<V2的邊 */
 82                 ESet[ECount].V1 = V;
 83                 ESet[ECount].V2 = W->AdjV;
 84                 ESet[ECount++].Weight = W->Weight;
 85             }
 86     /* 初始化為最小堆 */
 87     for ( ECount=Graph->Ne/2; ECount>=0; ECount-- )
 88         PercDown( ESet, ECount, Graph->Ne );
 89 }
 90  
 91 int GetEdge( Edge ESet, int CurrentSize )
 92 { /* 給定當前堆的大小CurrentSize,將當前最小邊位置彈出並調整堆 */
 93  
 94     /* 將最小邊與當前堆的最后一個位置的邊交換 */
 95     Swap( &ESet[0], &ESet[CurrentSize-1]);
 96     /* 將剩下的邊繼續調整成最小堆 */
 97     PercDown( ESet, 0, CurrentSize-1 );
 98  
 99     return CurrentSize-1; /* 返回最小邊所在位置 */
100 }
101 /*-------------------- 最小堆定義結束 --------------------*/
102  
103  
104 int Kruskal( LGraph Graph, LGraph MST )
105 { /* 將最小生成樹保存為鄰接表存儲的圖MST,返回最小權重和 */
106     WeightType TotalWeight;
107     int ECount, NextEdge;
108     SetType VSet; /* 頂點數組 */
109     Edge ESet;    /* 邊數組 */
110  
111     InitializeVSet( VSet, Graph->Nv ); /* 初始化頂點並查集 */
112     ESet = (Edge)malloc( sizeof(struct ENode)*Graph->Ne );
113     InitializeESet( Graph, ESet ); /* 初始化邊的最小堆 */
114     /* 創建包含所有頂點但沒有邊的圖。注意用鄰接表版本 */
115     MST = CreateGraph(Graph->Nv);
116     TotalWeight = 0; /* 初始化權重和     */
117     ECount = 0;      /* 初始化收錄的邊數 */
118  
119     NextEdge = Graph->Ne; /* 原始邊集的規模 */
120     while ( ECount < Graph->Nv-1 ) {  /* 當收集的邊不足以構成樹時 */
121         NextEdge = GetEdge( ESet, NextEdge ); /* 從邊集中得到最小邊的位置 */
122         if (NextEdge < 0) /* 邊集已空 */
123             break;
124         /* 如果該邊的加入不構成回路,即兩端結點不屬於同一連通集 */
125         if ( CheckCycle( VSet, ESet[NextEdge].V1, ESet[NextEdge].V2 )==true ) {
126             /* 將該邊插入MST */
127             InsertEdge( MST, ESet+NextEdge );
128             TotalWeight += ESet[NextEdge].Weight; /* 累計權重 */
129             ECount++; /* 生成樹中邊數加1 */
130         }
131     }
132     if ( ECount < Graph->Nv-1 )
133         TotalWeight = -1; /* 設置錯誤標記,表示生成樹不存在 */
134  
135     return TotalWeight;
136 }
鄰接表存儲 - Kruskal算法

 

三、Prim算法

此算法可以稱為”加點法“,每次迭代選擇代價最小的邊對應的點,加入到最小生成樹中。

算法從某一頂點s開始,逐漸長達覆蓋整個連通網的所有頂點。

1.圖的所有頂點集合為V;初始令集合u={s},v=Vu;

2.在兩個集合u,v能過夠組成的邊中,選擇一條代價最小的邊(u0,v0),加入到最小生成樹中,並把v0並入到集合u中。

3.重復上述步驟,直到最小生成樹有n-1條邊或者n個頂點為止。

 

 1 void Prim()
 2 {
 3     MST = {s};
 4     while(1) {
 5         V = 未收錄頂點中dist最小者;
 6         if(這樣的V不存在)
 7             break;
 8         將V收錄進MST:dist[V] = 0;
 9         for(V的每個鄰接點W)
10             if(dist[W]!=0)
11                 if(E(v,w) < dist[W]) {
12                     dist[W] = E(v,w);
13                     parent[W] = V;
14                 }
15     }
16     if(MST中收的頂點不到|V|個)
17         Error("生成樹不存在");
18 }
Prim邏輯

 T=O(|V|2)

 1     /* 鄰接矩陣存儲 - Prim最小生成樹算法 */
 2      
 3     Vertex FindMinDist( MGraph Graph, WeightType dist[] )
 4     { /* 返回未被收錄頂點中dist最小者 */
 5         Vertex MinV, V;
 6         WeightType MinDist = INFINITY;
 7      
 8         for (V=0; V<Graph->Nv; V++) {
 9             if ( dist[V]!=0 && dist[V]<MinDist) {
10                 /* 若V未被收錄,且dist[V]更小 */
11                 MinDist = dist[V]; /* 更新最小距離 */
12                 MinV = V; /* 更新對應頂點 */
13             }
14         }
15         if (MinDist < INFINITY) /* 若找到最小dist */
16             return MinV; /* 返回對應的頂點下標 */
17         else return ERROR;  /* 若這樣的頂點不存在,返回-1作為標記 */
18     }
19      
20     int Prim( MGraph Graph, LGraph MST )
21     { /* 將最小生成樹保存為鄰接表存儲的圖MST,返回最小權重和 */
22         WeightType dist[MaxVertexNum], TotalWeight;
23         Vertex parent[MaxVertexNum], V, W;
24         int VCount;
25         Edge E;
26          
27         /* 初始化。默認初始點下標是0 */
28            for (V=0; V<Graph->Nv; V++) {
29             /* 這里假設若V到W沒有直接的邊,則Graph->G[V][W]定義為INFINITY */
30                dist[V] = Graph->G[0][V];
31                parent[V] = 0; /* 暫且定義所有頂點的父結點都是初始點0 */ 
32         }
33         TotalWeight = 0; /* 初始化權重和     */
34         VCount = 0;      /* 初始化收錄的頂點數 */
35         /* 創建包含所有頂點但沒有邊的圖。注意用鄰接表版本 */
36         MST = CreateGraph(Graph->Nv);
37         E = (Edge)malloc( sizeof(struct ENode) ); /* 建立空的邊結點 */
38                 
39         /* 將初始點0收錄進MST */
40         dist[0] = 0;
41         VCount ++;
42         parent[0] = -1; /* 當前樹根是0 */
43      
44         while (1) {
45             V = FindMinDist( Graph, dist );
46             /* V = 未被收錄頂點中dist最小者 */
47             if ( V==ERROR ) /* 若這樣的V不存在 */
48                 break;   /* 算法結束 */
49                  
50             /* 將V及相應的邊<parent[V], V>收錄進MST */
51             E->V1 = parent[V];
52             E->V2 = V;
53             E->Weight = dist[V];
54             InsertEdge( MST, E );
55             TotalWeight += dist[V];
56             dist[V] = 0;
57             VCount++;
58              
59             for( W=0; W<Graph->Nv; W++ ) /* 對圖中的每個頂點W */
60                 if ( dist[W]!=0 && Graph->G[V][W]<INFINITY ) {
61                 /* 若W是V的鄰接點並且未被收錄 */
62                     if ( Graph->G[V][W] < dist[W] ) {
63                     /* 若收錄V使得dist[W]變小 */
64                         dist[W] = Graph->G[V][W]; /* 更新dist[W] */
65                         parent[W] = V; /* 更新樹 */
66                     }
67                 }
68         } /* while結束*/
69         if ( VCount < Graph->Nv ) /* MST中收的頂點不到|V|個 */
70            TotalWeight = ERROR;
71         return TotalWeight;   /* 算法執行完畢,返回最小權重和或錯誤標記 */
72     }
Prim算法

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM