最小生成树的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循环的执行。因此,最终生成树如图所示: