8.5 最小生成树—构造最小生成树的Kruskal算法
Kruskal 算法是一种按照网中边的权值递增的顺序构造最小生成树的方法。其基本思想是:设无向连通网为G=(V,E),令G 的最小生成树为T,其初态为T=(V,{}),即开始时,最小生成树T 由图G 中的n 个顶点构成,顶点之间没有一条边,这样T 中各顶点各自构成一个连通分量。然后,按照边的权值由小到大的顺序,考察G 的边集E 中的各条边。若被考察的边的两个顶点属于T 的两个不同的连通分量,则将此边作为最小生成树的边加入到T 中,同时把两个连通分量连接为一个连通分量;若被考察边的两个顶点属于同一个连通分量,则舍去此边,以免造成回路,如此下去,当T 中的连通分量个数为1 时,此连通分量便为G 的一棵最小生成树。
对于图8.23(a)所示的网,按照Kruskal 方法构造最小生成树的过程如图8.25 所示。在构造过程中,按照网中边的权值由小到大的顺序,不断选取当前未被选取的边集中权值最小的边。依据生成树的概念,n 个结点的生成树,有n-1 条边,故反复上述过程,直到选取了n-1 条边为止,就构成了一棵最小生成树。
下面介绍Kruskal 算法的实现。
设置一个结构数组Edges 存储网中所有的边,边的结构类型包括构成的顶点信息和边权值,定义如下:
#define MAXEDGE <图中的最大边数>
typedef struct {
elemtype v1;
elemtype v2;
int cost;
} EdgeType;
EdgeType edges[MAXEDGE];
在结构数组edges 中,每个分量edges[i]代表网中的一条边,其中edges[i].v1 和edges[i].v2 表示该边的两个顶点,edges[i].cost 表示这条边的权值。为了方便选取当前权值最小的边,事先把数组edges 中的各元素按照其cost 域值由小到大的顺序排列。在对连通分量合并时,采用7.5.2 节所介绍的集合的合并方法。对于有n 个顶点的网,设置一个数组father[n],其初值为father[i]=-1(i=0,1,…,n-1),表示各个顶点在不同的连通分量上,然后,依次取出edges 数组中的每条边的两个顶点,查找它们所属的连通分量,假设vf1和vf2 为两顶点所在的树的根结点在father 数组中的序号,若vf1 不等于vf2,表明这条边的两个顶点不属于同一分量,则将这条边作为最小生成树的边输出,并合并它们所属的两个连通分量。
下面用C 语言实现Kruskal 算法,其中函数Find 的作用是寻找图中顶点所在树的根结点在数组father 中的序号。需说明的是,在程序中将顶点的数据类型定义成整型,而在实际应用中,可依据实际需要来设定。
typedef int elemtype;
typedef struct {
elemtype v1;
elemtype v2;
int cost;
}EdgeType;
void Kruskal(EdgeType edges[ ],int n)
/*用Kruskal 方法构造有n 个顶点的图edges 的最小生成树*/
{ int father[MAXEDGE];
int i,j,vf1,vf2;
for (i=0;i<n;i++) father[i]=-1;
i=0;j=0;
while(i<MAXEDGE && j<n-1)
{ vf1=Find(father,edges[i].v1);
vf2=Find(father,edges[i].v2);
if (vf1!=vf2)
{ father[vf2]=vf1;
j++;
printf(“%3d%3d\n”,edges[i].v1,edges[i].v2);
}
i++;
}
}
算法8.15
int Find(int father[ ],int v)
/*寻找顶点v 所在树的根结点*/
{ int t;
t=v;
while(father[t]>=0)
t=father[t];
return(t);
}
算法8.16
在Kruskal 算法中,第二个while 循环是影响时间效率的主要操作,其循环次数最多为MAXEDGE 次数,其内部调用的Find 函数的内部循环次数最多为n,所以Kruskal 算法的时间复杂度为O(n·MAXEDGE)。
对于图8.23(a)所示的网,按照Kruskal 方法构造最小生成树的过程如图8.25 所示。在构造过程中,按照网中边的权值由小到大的顺序,不断选取当前未被选取的边集中权值最小的边。依据生成树的概念,n 个结点的生成树,有n-1 条边,故反复上述过程,直到选取了n-1 条边为止,就构成了一棵最小生成树。
下面介绍Kruskal 算法的实现。
设置一个结构数组Edges 存储网中所有的边,边的结构类型包括构成的顶点信息和边权值,定义如下:
#define MAXEDGE <图中的最大边数>
typedef struct {
elemtype v1;
elemtype v2;
int cost;
} EdgeType;
EdgeType edges[MAXEDGE];
在结构数组edges 中,每个分量edges[i]代表网中的一条边,其中edges[i].v1 和edges[i].v2 表示该边的两个顶点,edges[i].cost 表示这条边的权值。为了方便选取当前权值最小的边,事先把数组edges 中的各元素按照其cost 域值由小到大的顺序排列。在对连通分量合并时,采用7.5.2 节所介绍的集合的合并方法。对于有n 个顶点的网,设置一个数组father[n],其初值为father[i]=-1(i=0,1,…,n-1),表示各个顶点在不同的连通分量上,然后,依次取出edges 数组中的每条边的两个顶点,查找它们所属的连通分量,假设vf1和vf2 为两顶点所在的树的根结点在father 数组中的序号,若vf1 不等于vf2,表明这条边的两个顶点不属于同一分量,则将这条边作为最小生成树的边输出,并合并它们所属的两个连通分量。
下面用C 语言实现Kruskal 算法,其中函数Find 的作用是寻找图中顶点所在树的根结点在数组father 中的序号。需说明的是,在程序中将顶点的数据类型定义成整型,而在实际应用中,可依据实际需要来设定。
typedef int elemtype;
typedef struct {
elemtype v1;
elemtype v2;
int cost;
}EdgeType;
void Kruskal(EdgeType edges[ ],int n)
/*用Kruskal 方法构造有n 个顶点的图edges 的最小生成树*/
{ int father[MAXEDGE];
int i,j,vf1,vf2;
for (i=0;i<n;i++) father[i]=-1;
i=0;j=0;
while(i<MAXEDGE && j<n-1)
{ vf1=Find(father,edges[i].v1);
vf2=Find(father,edges[i].v2);
if (vf1!=vf2)
{ father[vf2]=vf1;
j++;
printf(“%3d%3d\n”,edges[i].v1,edges[i].v2);
}
i++;
}
}
算法8.15
int Find(int father[ ],int v)
/*寻找顶点v 所在树的根结点*/
{ int t;
t=v;
while(father[t]>=0)
t=father[t];
return(t);
}
算法8.16
在Kruskal 算法中,第二个while 循环是影响时间效率的主要操作,其循环次数最多为MAXEDGE 次数,其内部调用的Find 函数的内部循环次数最多为n,所以Kruskal 算法的时间复杂度为O(n·MAXEDGE)。