前面说的普利姆算法是以某顶点为起点,逐步找各顶点上最小权值的边,来构建最小生成树的。这就像是我们如果去参观某个展会,一种方法是从入口进去后,到最近的场馆观光,看完后再紧挨着看下一个;还有例外一个方法,实现计划好所有路线,进展会后直接去最想看的场馆,也就相当于去之前做好了攻略。
直接找最小权值的边来构建最小生成树,只不过每次构建是要考虑是否形成环路。
同样是这个题:假如你是一位工程师,需要为一个镇的九个村庄架设通信网络做设计,村庄位置大值如下图:
其中 V0~V8 是村庄,之间连线的数字表示村与村间的可通达的直线距离,比如 V0 至 V1 就是 10 千米(个别如 V0 与 V6,V6 与 V8,V5 与 V7 为测算距离是因为有高山或者湖泊,不予考虑)。你们领导要求你必须用最小的成本完成本次任务。该怎么做?
样例输入
9 15
0 1 10
0 5 11
1 6 16
6 5 17
1 2 18
1 8 12
2 8 8
2 3 22
3 8 21
3 6 24
6 7 19
3 7 16
3 4 20
4 5 26
4 7 7
样例输出
(4,7) 7
(2,8) 8
(0,1) 10
(0,5) 11
(1,8) 12
(1,6) 16
(3,7) 16
(6,7) 19
解题思路
同样的要用一个方法存储输入的信息, Prim 算法是用的二维邻接矩阵存储,而 Kruskal 算法是用结构体存储,分别用 begin、end、weight 代表存入的一组数据。
因为每次都要找最小的权值,找到后删除该数,再找最小权值。这样的话 ,可以在进入 MiniSpanTree_Kruskal 函数之前就排好序,如图所示(按权值从小到大排序)。
然后主要是通过 Find 函数判断两点之间是否已经连通,Find 函数代码:
int Find(int parent[],int f)
{
while(parent[f]>0)//循环直至以当前点为下标的点为 0,此时没有其余连接的点(如果在同一颗生成树中,通过 Find函数可以实现从不同分支可以到达同一个点)
f=parent[f];
return f;
}
然后解决了权值的排序问题和生成树出现环路的问题之后,代码就好理解了。
代码如下:
#include<stdio.h>
int n,m;
//用结构体存储输入的信息
struct node
{
int begin;
int end;
int weight;//权值
};
struct node k[1000],t;
//用来找两者共同到达的点,相同的话就已经在生成树里面了,不相同的话就没有在生成树中
int Find(int parent[],int f)//并查集
{
while(parent[f]>0)//循环直至以当前点为下标的点为 0,此时没有其余连接的点(如果在同一颗生成树中,通过 Find函数可以实现从不同分支可以到达同一个点)
f=parent[f];
return f;
}
void MiniSpanTree_Kruskal()
{
int i,d,b;
int parent[1000];
for(i=0;i<n;i++)
{
parent[i]=0;//把数组初始化为 0,
}
for(i=0;i<m;i++)
{
d=Find(parent,k[i].begin);//找 k[i].begin对应的值
b=Find(parent,k[i].end);//找 k[i].end对应的值
if(d!=b)//不相等的话说明连接起来不会形成回路
{
parent[d]=b;//通过这个步骤,说明两点已经连接起来
printf("(%d,%d) %d\n",k[i].begin,k[i].end,k[i].weight);
}
}
}
int main()
{
int i,j;
scanf("%d %d",&n,&m);
for(i=0;i<m;i++)
scanf("%d %d %d",&k[i].begin,&k[i].end,&k[i].weight);
for(i=0;i<m-1;i++)//冒泡排序
{
for(j=0;j<m-i-1;j++)
{
if(k[j].weight>k[j+1].weight)
{
t=k[j];//提前定义好结构体变量 t(注意这里不要写成 k[j].weight)
k[j]=k[j+1];
k[j+1]=t;
}
}
}
MiniSpanTree_Kruskal();
}
总结
prim 算法适用于边稠密的情况;它是从某一顶点开始构建树,直至所有顶点都进入生成树;顶点数为 n,时间复杂度为 O();
Kruskal 算法适用于边稀疏的情况;它是从某一边开始构建树,直至所有顶点都连通;边长数为 e,时间复杂度为 O( e log e)。