最小生成樹的Kruskal算法實現


最近在復習數據結構,所以想起了之前做的一個最小生成樹算法。用Kruskal算法實現的,結合堆排序可以復習回顧數據結構。現在寫出來與大家分享。

  最小生成樹算法思想:書上說的是在一給定的無向圖G = (V, E) 中,(u, v) 代表連接頂點 u 與頂點 v 的邊(即),而 w(u, v) 代表此邊的權重,若存在 T 為 E 的子集(即)且為無循環圖,使得的 w(T) 最小,則此 T 為 G 的最小生成樹。說白了其實就是在含有 n 個頂點的連通網中選擇 n-1 條邊,構成一棵極小連通子圖,並使該連通子圖中 n-1 條邊上權值之和達到最小,則稱最小生成樹

  本程序用的是克魯斯卡爾算法(Kruskal),也可以使用prim算法實現。Kruskal思想是在帶權連通圖中,不斷地在排列好的邊集合中找到最小的邊,如果該邊滿足得到最小生成樹的條件,就將其構造,直到最后得到一顆最小生成樹。

 圖的頂點存儲結構體:

1 //結構體定義,儲存圖的頂點
2 typedef struct { 
3     int from; //邊的起始頂點
4     int to;   //邊的終止頂點
5     int cost; //邊的權值
6 }Edge;

 問題:頂點編號的類型。

  好的程序應該可以擴展,不論頂點用0,1,2...  順序編號還是用5,2,1,7... 亂序編號還是用a,b,c...  英文編號都應該可以做到兼容通過,所以在存儲圖的節點的時候我做了一個映射,就是不論輸入的什么編號一律轉換成順序編號0,1,2...,在最后輸出的時候再把編號映射回原來的編號,這樣就可以應對不同而頂點編號。如下面程序:

 1 for(i = 0;i < edgeNum; i++){
 2     printf("請輸入第 %d 條邊!\n",i+1);
 3     scanf(" %c %c %d",&from,&to,&cost); 
 4     edge_temp[i][0] = judge_num(from); 
 5     edge_temp[i][1] = judge_num(to);
 6     edge_temp[i][2] = cost; 
 7 }
 8 //對輸入的邊和點信息進行堆排序
 9 HeapSort(edge_temp,edgeNum);
10 int j;
11 for(j = 0;j < edgeNum; j++){
12     edge[j].from = edge_temp[j][0]; 
13     edge[j].to = edge_temp[j][1]; 
14     edge[j].cost = edge_temp[j][2];   
15 }

每次輸入頂點后都會先保存到臨時存儲數組edge_temp中,進行堆排序后再把數據白存在真正的數據中。其中判斷是否形成回路借助了一個遞歸方法:

1 //用於判斷是否形成回路
2 int judge(int num){  
3     if(num == judge_temp[num]){
4         return judge_temp[num]; 
5     } 
6     return judge_temp[num] = judge(judge_temp[num]);  
7 }

執行步驟:

1:在帶權連通圖中,將邊的權值排序(本程序用的是堆排序);

2:判斷是否需要選擇這條邊(此時的邊已按權值從小到大排好序)。判斷的依據是邊的兩個頂點是否已連通,如果連通則繼續下一條;如果不連通,那么就選擇使其連通。

3:循環第二步,直到圖中所有的頂點都在同一個連通分量中,即得到最小生成樹。

判斷法則:(當將邊加入到已找到邊的集合中時,是否會形成回路?)

  1:如果沒有形成回路,那么直接將其連通。此時,對於邊的集合又要做一次判斷:這兩個點是否在已找到點的集合中出現過?如果兩個點都沒有出現過,那么將這 兩個點都加入已找到點的集合中;如果其中一個點在集合中出現過,那么將另一個沒有出現過的點加入到集合中;如果這兩個點都出現過,則不用加入到集合中。

   2:如果形成回路,不符合要求,直接進行下一次操作。

重點類容就這么多,下面給出源程序,程序直接復制后可以運行,有興趣的朋友也可以用prim算法實現。

  1 #include <stdio.h> 
  2 #include <string.h> 
  3 //常量定義,邊點最大數量限制50;
  4 #define MAXE 52
  5 
  6 /*
  7  * Info:最小生成樹算法源碼(C語言版)
  8  * @author: zhaoyafei 
  9  * time: 2015
 10  */
 11 
 12 //結構體定義,儲存圖的頂點
 13 typedef struct { 
 14     int from; //邊的起始頂點
 15     int to;   //邊的終止頂點
 16     int cost; //邊的權值
 17 }Edge;
 18 
 19 int nodeNum;  //頂點數;
 20 int edgeNum;  //邊數;
 21 int min_cost; //記錄最小生成樹(權值)
 22 int judge_temp[MAXE]; //記錄判斷是否成環
 23 int sort[MAXE][MAXE]; //用來做排序
 24 int edge_temp[MAXE][3]; //用於存儲堆排序邊點信息
 25 
 26 Edge edge[MAXE];        //用於存儲邊點信息
 27 Edge min_edge[MAXE];    //用於存儲最小生成樹邊點信息
 28 
 29 char judge_num_int[MAXE]; 
 30 int inputs = 1;
 31 void HeapSort(int array[MAXE][3],int length);
 32 int judge_num(char from);
 33 
 34 //save_point()函數,存儲圖的點邊信息; 
 35 void save_point(){  
 36     char from;
 37     char to;
 38     int cost = 0; 
 39     int i;  
 40     for(i = 0;i < edgeNum; i++){
 41         printf("請輸入第 %d 條邊!\n",i+1);
 42         scanf(" %c %c %d",&from,&to,&cost); 
 43 
 44         edge_temp[i][0] = judge_num(from); 
 45         edge_temp[i][1] = judge_num(to);
 46         edge_temp[i][2] = cost; 
 47     }
 48     //對輸入的邊和點信息進行堆排序
 49     HeapSort(edge_temp,edgeNum);
 50     int j;
 51     for(j = 0;j < edgeNum; j++){
 52         edge[j].from = edge_temp[j][0]; 
 53         edge[j].to = edge_temp[j][1]; 
 54         edge[j].cost = edge_temp[j][2];   
 55     }
 56 } 
 57 
 58 int judge_num(char str){
 59     int n1 = 0;
 60     for(int j1 = 1;j1 < edgeNum * 2; j1++){
 61         if(str == judge_num_int[j1]){
 62             n1++;
 63         }
 64     }
 65     if(n1 == 0){
 66         judge_num_int[inputs] = str;
 67         inputs++;
 68     }
 69     int return_num = 1;
 70     for(int j2 = 1;j2 < edgeNum * 2; j2++){
 71         if(str == judge_num_int[j2]){
 72             return_num = j2;
 73         }
 74     }
 75     return return_num;
 76 }
 77 
 78 //用於判斷是否形成回路
 79 int judge(int num){  
 80     if(num == judge_temp[num]){
 81         return judge_temp[num]; 
 82     } 
 83     return judge_temp[num] = judge(judge_temp[num]);  
 84 } 
 85 
 86 //判斷是否是一棵最小生成樹
 87 bool is_judge(){   
 88     int oneedge = judge(1);
 89     int i;    
 90     for(i = 2;i <= nodeNum; i++)  {  
 91         if(oneedge != judge(i)){  
 92             return false;  
 93         }  
 94     }      
 95     return true;  
 96 }
 97 
 98 //kruskal算法   
 99 void kruskal(){  
100     min_cost = 0;//最小生成樹 
101     //初始化輔助回路判斷數組  
102     int m;   
103     for(m = 0;m < MAXE;m++)  {  
104         judge_temp[m] = m;  
105     }  
106       
107     int edge_num = 0;//記錄最小生成樹的邊數   
108     int i;
109     for(i = 0;i < edgeNum; i++){
110         //小於總節點數
111         if(edge_num != nodeNum - 1){
112             int edge_from = judge(edge[i].from);  
113             int edge_to = judge(edge[i].to);
114             //如果形成回路則edge_from == edge_to;
115             if(edge_from != edge_to){
116                 //如果沒有形成回路,則改變原臨時數組中的值  
117                 judge_temp[edge_from] = edge_to;  
118                 min_cost += edge[i].cost;  
119 
120                 //將符合的邊加入到存儲數組中
121                 min_edge[edge_num].from = edge[i].from;  
122                 min_edge[edge_num].to = edge[i].to;  
123                 min_edge[edge_num].cost = edge[i].cost; 
124                 
125                 edge_num++;  
126             }   
127         }   
128     }  
129 }
130 
131 //array是待調整的堆數組,i是待調整的數組元素的位置,nlength是數組的長度
132 //根據數組array構建大頂堆
133 void HeapAdjust(int array[MAXE][3],int i,int nLength){
134    int nChild;
135    for(; 2*i + 1 < nLength; i = nChild){  //子結點的位置=2*(父結點位置)+1
136         nChild = 2*i + 1;
137         //得到子結點中較大的結點
138         if(nChild < nLength-1 && array[nChild+1][2] > array[nChild][2]){
139             ++nChild;
140         }
141         //如果較大的子結點大於父結點那么把較大的子結點往上移動,替換它的父結點
142         if(array[i][2] < array[nChild][2]){
143             int temp_arr2[3];
144             temp_arr2[0] = array[i][0];
145             temp_arr2[1] = array[i][1];
146             temp_arr2[2] = array[i][2];
147 
148             array[i][0] = array[nChild][0];
149             array[i][1] = array[nChild][1];
150             array[i][2] = array[nChild][2];
151 
152             array[nChild][0] = temp_arr2[0];
153             array[nChild][1] = temp_arr2[1];
154             array[nChild][2] = temp_arr2[2];
155         }else{
156             break;//否則退出循環
157         }
158     }
159 }
160  
161 //堆排序算法
162 void HeapSort(int array[MAXE][3],int length){
163     //調整序列的前半部分元素,調整完之后第一個元素是序列的最大的元素
164     //length/2-1是最后一個非葉節點,此處"/"為整除
165     int j;
166     for( j= length/2 - 1; j >= 0; --j){
167         HeapAdjust(array,j,length);
168     }
169     //從最后一個元素開始對序列進行調整,不斷的縮小調整的范圍直到第一個元素
170     int i;
171     for(i = length - 1; i > 0; --i){
172         //把第一個元素和當前的最后一個元素交換,
173         //保證當前的最后一個位置的元素都是在現在的這個序列之中最大的
174         int temp_arr1[3]; //構建二維數組的原因:交換后保證數組中其他屬性值同時交換
175         temp_arr1[0] = array[i][0];
176         temp_arr1[1] = array[i][1];
177         temp_arr1[2] = array[i][2];
178 
179         array[i][0] = array[0][0];
180         array[i][1] = array[0][1];
181         array[i][2] = array[0][2];
182 
183         array[0][0] = temp_arr1[0];
184         array[0][1] = temp_arr1[1];
185         array[0][2] = temp_arr1[2];
186 
187         //不斷縮小調整heap的范圍,每一次調整完畢保證第一個元素是當前序列的最大值
188         HeapAdjust(array,0,i);
189     }
190 }
191 
192 //輸出最小生成樹的信息(包括邊點和權值) 
193 void output(){   
194     if(is_judge()){  
195         printf("最小生成樹:\n"); 
196         printf("起點 -> 終點   路徑長:\n");   
197         for(int i = 0;i < nodeNum-1; i++){  
198             printf(" %c   ->  %c       %d\n",judge_num_int[min_edge[i].from],judge_num_int[min_edge[i].to],min_edge[i].cost);  
199         }  
200         printf("min cost is : %d\n",min_cost); 
201         printf("*******************************************************************************\n"); 
202         printf("請輸入 節點數 邊數(中間需用空格隔開):\n");
203     }else{
204         printf("最小生成樹不存在!\n");
205         printf("*******************************************************************************\n"); 
206         printf("請輸入 節點數 邊數(中間需用空格隔開):\n");  
207     } 
208 }
209 
210 /*
211  * 程序主方法;
212  * 用於開始程序,介紹程序操作須知;
213  */
214 int main(){ 
215     printf("*******************************************************************************\n"); 
216     printf("**                         最小生成樹(kruskal算法)                        ***\n");
217     printf("**  說明:開始程序輸入圖的總點數和總邊數,測試程序目前邊點限制最多輸入50個  ***\n");
218     printf("**        中間用空格隔開。輸入邊和點信息,需要按格式:開始邊 終止邊  權值   ***\n");
219     printf("**        本次計算結束可以按要求開始下次計算。                              ***\n");
220     printf("*******************************************************************************\n");
221     printf("請輸入 節點數 邊數(中間需用空格隔開):\n");
222     while(scanf("%d%d",&nodeNum,&edgeNum) != EOF){ 
223         //判斷輸入的邊和點的合法性
224         if(nodeNum < 1 || edgeNum < 1){
225             printf("輸入的數據不合法\n");
226             printf("請輸入 節點數 邊數(中間需用空格隔開):\n");
227             return 0;
228         }else if(nodeNum > 50 || edgeNum > 50){
229             printf("輸入的邊或者點不能大於50\n");
230             printf("請輸入 節點數 邊數(中間需用空格隔開):\n");
231             return 0;
232         }else{
233             printf("請輸入 開始節點 終止節點 該邊的權值(中間需用空格隔開,回車換行):\n");
234             printf("共 %d 條邊\n",edgeNum);
235             for(int m = 0;m < MAXE; m++)  {  
236                 judge_num_int[m] = '-';  
237             }  
238             inputs = 1;
239             save_point(); //存儲邊點信息          
240             kruskal();    //算法執行 
241             output();     //輸出執行結果
242         }  
243     } 
244     return 0;  
245 }

運行結果如下:

 


免責聲明!

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



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