最小生成樹的Kruskal算法


    庫魯斯卡爾(Kruskal)算法是一種按照連通網中邊的權值遞增的順序構造最小生成樹的方法。Kruskal算法的基本思想是:假設連通網G=(V,E),令最小生成樹的初始狀態為只有n個頂點而無邊的非連通圖T=(V,{}),圖中每個頂點自成一個連通分量。在E中選擇權值最小的邊,若該邊依附的頂點落在T中不同的連通分量中,則將此邊加入到T中;否則,舍去此邊而選下一條權值最小的邊;依次類推,直到T中所有頂點都在同一個連通分量上(此時含有n-1邊)為止,這時的T就是一棵最小的生成樹。
    注意,初始時T的連通分量為頂點個數n,在每一次選取最小權值的邊加入到T時一定要保證T的連通分量減1;也即選取最小權值邊所連接的兩個頂點必須位於不同的連接分量上,否則應舍去此邊而再選取下一條最小權值的邊。
 
    概述
    實現Kruskal算法的關鍵是如何判斷所選取的邊是否與生成樹中已保留的邊形成回路,這可通過判斷邊的兩個頂點所在的連通分量的方法來解決.為此設置一個輔助數組vest(數組元素下標為0~n-1),它用於判斷兩個頂點集合(即兩個連通分量),此時按其中的一個集合編號重新統一編號(即合並成一個連通分量)。因此,當兩個頂點的集合(連通分量)編號不同時,則加入這兩個頂點所構成的邊到最小生成樹中就一定不會形成回路,因為這兩個頂點分屬於不同的連通分量。
    在實現Kruskal算法時,需要用一個數組E來存放圖G中是所有邊,並要求他們是按權值由小到大的順序排列的;為此先從圖G的鄰接矩陣中獲取所有邊集E(注意,在連接矩陣中頂點i和頂點j存在着(i,j)和(j,i)兩條邊,故只取i<j時的一條邊,然后用冒泡排序法對邊集E按權值遞增排序。

參考代碼:
 1 #include<stdio.h>
 2 #define MAXSIZE 30
 3 #define MAXCOST 32767
 4 
 5 typedef struct
 6 {
 7     int u;//邊的起始頂點
 8     int v;//邊的起始終點
 9     int w;//邊的權值
10 }Edge;
11 
12 void  Bubblesort(Edge R[],int e)//冒泡排序,對數組R中的e條邊按權值遞增排序
13 {
14     Edge temp;
15     int i,j,swap;
16     for(i=0;i<e-1;j++)//進行e-1趟排序
17     {
18      swap=0;
19      for(j=0;j<e-i-1;j++)
20          if(R[j].w>R[j+1].w)
21          {
22              temp=R[j];R[j]=R[j+1];R[j+1]=temp;//交換R[j]和R[j+1]
23              swap=1;//置有交換標志
24          }
25          if(swap==0) break;//本趟比較中未出現交換則結束排序
26     }
27 }
28 
29 void Kruskal(int gm[][6],int n)//在頂點為n的連接圖中構造最小的生成樹,gm為連通網的鄰接矩陣
30 {
31     int i,j,u1,v1,sn1,sn2,k;
32     int vest[MAXSIZE];//數組vest用於判斷兩頂點之間是否連通
33     Edge E[MAXSIZE];//MAXSIZE為可存放邊數的最大常量值
34     k=0;
35     for(i=0;i<n;i++)
36         for(j=0;j<n;j++)
37         if(i<j&&gm[i][j]!=MAXCOST)//MAXCOST為一個極大的常量值
38         {
39             E[k].u=i;
40             E[k].v=j;
41             E[k].w=gm[i][j];
42             k++;
43         }
44         Bubblesort(E,k);//采用冒泡排序對數組E中的k條邊按權值遞增排序
45         for(i=0;i<n;i++)//初始化輔助數組
46             vest[i]=i;//給每個頂點置不同連通分量編號,即初始時有n個連通分量
47         k=1;//k表示當前構造生成樹的第n條邊,初始值為1
48         j=0;//j為數組E中元素的下標,初值為0
49         while(k<n)//產生最小生成樹的n-1條邊
50         {
51             u1=E[j].u;v1=E[j].v;//取一條邊的頭尾頂點
52             sn1=vest[u1];
53             sn2=vest[v1];//分別得到這兩個頂點所屬的集合編號
54             if(sn1!=sn2)//兩頂點分屬於不同集合則該邊為最小生成樹的一條邊
55             {
56                 printf("Edge:(%d,%d),Wight:%d\n",u1,v1,E[j].w);
57                 k++;//生成的邊數增1
58                 for(i=0;i<n;i++)//兩個集合統一編號
59                     if(vest[i]==sn2)//集合編號為sn2的第i號邊其邊號改為sn1
60                         vest[i]=sn1;
61             }
62             j++;//掃描下一條邊
63         }
64 }
65 void main()
66 {
67     int g[6][6]={{100,6,1,5,100,100},{6,100,5,100,3,100},{1,5,100,5,6,4},
68     {5,100,5,100,100,2},{100,3,6,100,100,6},{100,100,4,2,6,100}};
69     Kruskal(g,6);//生成最小生成樹
70 }

輸出結果:


數組E示意圖:


執行Kruskal算法中的冒泡排序函數BubbleSort后,存放連通網中所有邊的數組E如下圖所示。因數組E中前4條邊的權值最小且又滿足不在同一連通分量上的條件,故它們就是生成樹的邊(見圖a,b,c,d)。接着考慮當前權值最小邊(0,3)因該邊所連接的兩頂點在同一連通分量上,故舍棄此邊,然后再選擇下一權值最小的邊。這時k值已等於n(即已找到n-1條邊),故終止while循環的執行。因此,最終生成樹如圖所示:

 

 


免責聲明!

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



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