将边从小到大顺序排列,从最小边的开始,逐一将边加入最小生成树中,但有一个问题是要判断加入边后,是否会产生回路。解决方法:通过访问边的起点和终点是否在同一个集合来判断该边加入生成树后是否会形成环路。
克鲁斯卡尔算法实际上是并查集的一个应用。
关于并查集的讲解,我十分推荐数据结构4——并查集(入门)这篇博客。
将邻接矩阵转换成边集数组:
思路:由于是无向图,,邻接矩阵是一个对称矩阵,因此只需要遍历邻接矩阵的上半个三角形。
边集数组的结构定义:
typedef struct
{
int begin ;
int end ;
int weigth ;
}EdgeAdjlist[ MAXSIZE ] , EdgeNode ;
邻接矩阵转换成边集数组:
void TransEdge( EdgeAdjlist edge , MGraph G )
{
int i , j , k = 0 ;
EdgeNode t ;
for( i = 0 ; i < G.VertexNum ; i++ )
for( j = i + 1 ; j < G.VertexNum ; j++ )
if( G.arcs[ i ][ j ] > 0 && G.arcs[ i ][ j ] < MAXINT )
{
edge[ k ].begin = i ;
edge[ k ].end = j ;
edge[ k ].weigth = G.arcs[ i ][ j ] ;
k++ ;
}
for( i = 0 ; i < G.EdgeNum - 1 ; i++ )
for( j = 0 ; j < G.EdgeNum - i - 1 ; j++ )
if( edge[ j ].weigth > edge[ j + 1 ].weigth )
{
t = edge[ j ] ;
edge[ j ] = edge[ j+ 1 ] ;
edge[ j+ 1 ] = t ;
}
}
find函数:
创建一个parent数组来存储顶点的父节点,通过find函数来找到该节点所在集合的根节点,记住,parent[ i ] = 0 表示的是:i 结点本身就是结点所在集合的根节点,也就是说这个结点自己构成一个集合
int find( int x )
{
while( parent[ x ] > 0 )
x = parent[ x ] ;
return x ;
}
//该函数的优化:
int find( int x )
{
if( parent[ x ] > 0 )
x = find( parent[ x ] ) ;
return x ;
}
优化采用了压缩路径的方法,直接让子孙顶点指向了集合的根节点,这样就大大减少了以后寻找集合的根节点的时间。
克鲁斯卡尔算法:
void MiniSpanTree_Kruskal( MGraph G )
{
int i , n , m ;
EdgeAdjlist edge ;
TransEdge( edge , G ) ;
for( i = 0 ; i < G.VertexNum ; i++ )
parent[ i ] = 0 ;
for( i = 0 ; i < G.EdgeNum ; i++ )
{
n = find( edge[ i ].begin ) ;
m = find( edge[ i ].end ) ;
if( n != m )
{
parent[ n ] = m ;
printf( "%c ===== %c , %d\n" , G.vexs[ edge[ i ].begin ] , G.vexs[ edge[ i ].end ] , edge[ i ].weigth ) ;
}
}
}
有这样一个无向图(图是从《大话数据结构》里盗的):
其边集数组为:
我们来看看代码是如何让运行的:
还要再强调一遍的是:parent[ i ] = 0 表示的是:i 结点本身就是结点所在集合的根节点,也就是说这个结点自己构成一个集合
当i=0时:第一条边加入了最小生成树中,形成了这样一棵树:
同理i=1,i=2,又生成了两颗独立的树:
这时候,parent数组的值为:[ 1,0,8,0,7,0,0,0,0 ] ,说明了v0的父结点是v1,v2的v8,v4的是v7.
**当i=3时:要加入的边为v0-----v5,因为此时v0父节点是v1,v5父节点是自己,两个结点不在同一个集合内,因此可以加入这条边。最后v5成为了这个集合的根结点,**如图:
此时,parent的值为:[ 1,5,8,0,7,0,0,0,0 ]
i=4,5,6都是同理,最后生成了两颗独立的树:
此时的parent数组的值:[ 1,5,8,7,7,8,0,0,6 ]
但是当i=7时,要加入的边是v5------v6,通过find函数可知,v5所在集合的根节点是v6,因此v5和v6是在同一个集合里面,如果加入了这条边,那么就会形成回路!
同理,i=8时,要加入的边是v1-----v2,两个结点所在集合的根节点均为v6,这条边也不能加入。
当i=9时,边v6-----v7的两个结点所在集合的根节不是同一个结点,因此,这条边可以加入最小生成树中:
当 i 继续增大,后面的边均会生成环路。最终parent数组的值为:[ 1,5,8,7,7,8,7,0,6 ]
需要注意的是:这里的树并不是Kruskal生成的真正的树,就比如:上图的v8和v5再真正的生成子树中并没有直接关系。我们用parent数组标记生成子树的根节点,只是为了找到分别独立的集合中的一个代表,结点通过找到这个代表就能知道自己属于哪一个集合了,当然了,任何一个结点都能作为集合的代表。
最后生成的最小生成树:
再附上优化后的源代码:
#include<stdio.h>
#define MAXSIZE 100
#define MAXINT 65535
typedef struct
{
char vexs[ MAXSIZE ] ;
int arcs[ MAXSIZE ][ MAXSIZE ] ;
int VertexNum , EdgeNum ;
}MGraph ;
typedef struct EdgeNode
{
int begin ;
int end ;
int weigth ;
}EdgeAdjlist[ MAXSIZE ] , EdgeNode ;
void CreateMGraph( MGraph *G ) ;
int Locate( MGraph G , char ch ) ;
void TransEdge( EdgeAdjlist edge , MGraph G ) ;
void MiniSpanTree_Kruskal( MGraph G ) ;
int find( int x ) ;
int parent[ MAXSIZE ] ;
int main( void )
{
MGraph G ;
CreateMGraph( &G ) ;
MiniSpanTree_Kruskal( G ) ;
return 0 ;
}
int Locate( MGraph G , char ch )//找到结点在顶点数组中的下标
{
int i ;
for( i = 0 ; i < G.EdgeNum ; i++ )
if( ch == G.vexs[ i ] )
break ;
return i ;
}
void CreateMGraph( MGraph *G )//创建邻接矩阵
{
int i , weight , pos1 , pos2 ;
char x , y ;
printf( "请输入图中结点的个数:" ) ;
scanf( "%d" , &G->VertexNum ) ;
getchar() ;
for( i = 0 ; i < G->VertexNum ; i++ )
{
printf( "请输入第%d个结点的信息:" , i + 1 ) ;
scanf( "%c" , &G->vexs[ i ] ) ;
getchar() ;
}
printf( "请输入图的边的条数:" ) ;
scanf( "%d" , &G->EdgeNum ) ;
getchar() ;
for( i = 0 ; i< G->EdgeNum ; i++ )
{
printf( "请输入第%d条边的两个结点信息,和边的权值:" , i+ 1 ) ;
scanf( "%c %c %d" , &x , &y , &weight ) ;
getchar() ;
pos1 = Locate( *G , x ) ;
pos2 = Locate( *G , y ) ;
G->arcs[ pos1 ][ pos2 ] = weight ;
G->arcs[ pos2 ][ pos1 ] = weight ;
}
}
void TransEdge( EdgeAdjlist edge , MGraph G )
{
int i , j , k = 0 ;
EdgeNode t ;
for( i = 0 ; i < G.VertexNum ; i++ )
for( j = i + 1 ; j < G.VertexNum ; j++ )
if( G.arcs[ i ][ j ] > 0 && G.arcs[ i ][ j ] < MAXINT )
{
edge[ k ].begin = i ;
edge[ k ].end = j ;
edge[ k ].weigth = G.arcs[ i ][ j ] ;
k++ ;
}
for( i = 0 ; i < G.EdgeNum - 1 ; i++ )
for( j = 0 ; j < G.EdgeNum - i - 1 ; j++ )
if( edge[ j ].weigth > edge[ j + 1 ].weigth )
{
t = edge[ j ] ;
edge[ j ] = edge[ j+ 1 ] ;
edge[ j+ 1 ] = t ;
}
}
int find( int x )//找结点所在集合的根节点
{
if( parent[ x ] > 0 )
x = find( parent[ x ] ) ;
return x ;
}
void MiniSpanTree_Kruskal( MGraph G )
{
int i , n , m ;
EdgeAdjlist edge ;
TransEdge( edge , G ) ;
for( i = 0 ; i < G.VertexNum ; i++ )
parent[ i ] = 0 ;
for( i = 0 ; i < G.EdgeNum ; i++ )
{
n = find( edge[ i ].begin ) ;
m = find( edge[ i ].end ) ;
if( n != m )
{
parent[ n ] = m ;
printf( "%c ===== %c , %d\n" , G.vexs[ edge[ i ].begin ] , G.vexs[ edge[ i ].end ] , edge[ i ].weigth ) ;
}
}
}