克鲁斯卡尔算法(Kruskal算法)
对于n个顶点的连通图而言,其生成的最小生成树有n-1条边,即可以保证从任一点出发可以到达任一点且不产生回路。
克鲁斯卡尔算法(Kruskal算法):对每条边的权值进行从小到大排序,然后从小到大取权值最小的边,如取出的边会在树中产生回路则舍去,取下一条;若不会产生回路则加入到树中。
因此Kruskal算法的关键问题就是:如何判断新加入的边是否会产生回路。
判断是否会产生回路的方法为:在初始状态下给每个顶点赋予不同的标记,对于遍历过程的每条边,其都有两个顶点,判断这两个顶点的标记是否一致,如果一致,说明它们本身就处在一棵树中,如果继续连接就会产生回路;如果不一致,说明它们之间还没有任何关系,可以连接,同时连接后会将其顶点标记改变。
变化过程
以下图表示每个顶点标记状态的变化过程,两点颜色相同表示在同一边上,多点颜色相同表示在树上。
实现方法:
建立两个结构体数组,一个用于存储边的信息edge edges,一个用于记录各个点的标记assist assists
typedef struct edge{
VertexType initial; //起点
VertexType end;//终点
VertexType weight;//权值
}edge[MAX_VERtEX_NUM];
//定义辅助数组
typedef struct {
VertexType value;//顶点数据
int sign;//每个顶点所属的集合
}assist[MAX_VERtEX_NUM];
对edges进行升序排序,为方便观察用字母来表示起始点(实际存储的是int)。下面是edges(左)和 assists(右)的信息
当AC构成一条边时,判断是否构成回路,因为AC的标记不同,所以成功。 信息C的标记就变为A的标记,同时AC这条边就相当于在树中存在了。下次就会从edges中的DF进行判断,更新两个数组的信息
下次判断DF,成功,F的标记改为D的标记
同样BE
CF,判断成功,注意这时不仅需要把F变为C的标记,同时F后面连接的所有是树中的点都要改为C的标记,本例中FD都要改变
AD标记相同,不成功,判断BC,成功。至此一共产生5条边,对于6个顶点的最少生成树,已经找出了满足其条件的5条边了。
总结:对于新的树中的边,把终点及其所连接的所有是树中点的标记改为起始点的标记
如X——Y是新的边,x为起点,y为终点
连接后各点标记会改为
int main(){
int arcnum, vexnum;
edge edges;//边信息的数组
CreateUDN(&edges, &vexnum, &arcnum);
//对连通网中的所有边进行升序排序,结果仍保存在edges数组中 使用的是库函数
qsort(edges, arcnum, sizeof(edges[0]), cmp);
//创建一个空的结构体数组,用于存放最小生成树
edge minTree;
//设置一个用于记录最小生成树中边的数量的常量
int num = 0;
//遍历所有的边
for (int i = 0; i<arcnum; i++) {
//找到边的起始顶点和结束顶点在数组assists中的位置
int initial = Locatevex(vexnum, edges[i].initial);
int end = Locatevex(vexnum, edges[i].end);
//如果顶点位置存在且顶点的标记不同,说明不在一个集合中,不会产生回路
if (initial != -1 && end != -1 && assists[initial].sign != assists[end].sign) {
//记录该边,作为最小生成树的组成部分
minTree[num] = edges[i];
//计数+1
num++;
//将新加入生成树的顶点标记全部更改为一样的
for (int k = 0; k<vexnum; k++) {//这一步是关键
if (assists[k].sign == assists[end].sign) {
assists[k].sign = assists[initial].sign;
}
}
//如果选择的边的数量和顶点数相差1,证明最小生成树已经形成,退出循环
if (num == vexnum - 1) {
break;
}
}
}
//输出语句
for (int i = 0; i<vexnum - 1; i++) {
printf("%d,%d\n", minTree[i].initial, minTree[i].end);
}
return 0;
}