让我们先了解一下什么是最小生成树,就是在一个图里找到一颗树的权值和最小。
一.Kruskal算法
我们现在有如下的一张图:
求这个图的最小生成树,我们可以先给各权值边从小到大排序,排好序之后进行选边,从最小的开始,从无边开始加。
比如上图,最小的权值边是1和2连通的1,我们把这条边加上,就得到了:
接下重复第一个操作,下一个是1和3之间的权值为2:
接下来是4和6之间的边:
5和6之间的权值:
按照我们的步骤,接下来应该是2和3之间的权值
但是如果我们连接上这两个点,就构成了一个回路,这就不是树了,不能称作最小生成树,所以跳过
接下来,我们继续按顺序进行查找,到了4和5之间
这个不能要的思路和2与3不能连接一样,不能构成最小生成树
接下来就到了3和4之间
此时就已经构成了树,因为已经用了n-1个边,如果再加就不会是树。
这时也构成了这个图的最小生成树。
这就是Kruskal算法的描述,接下来让我们看一下代码实现:
# include <iostream>
# include <cstdio>
# include <algorithm>
using namespace std;
struct edge
{
int u,v,w;//这里用一个结构体来储存所给的所有关系,便于进行排序和查找。
}e[110];
int n,m;
int f[110]={0};//因为要判断是不是一个树,为了确定他没有回路,需要并查集进行判断。
bool cmp(edge a,edge b)//把边从小到大排序
{
return a.w<b.w;
}
void init()//并查集的初始化
{
int i;
for(i=1;i<=n;i++)
f[i]=i;
}
int getboss(int v)//并查集找boss
{
if(f[v]==v)
return v;
else
{
f[v]=getboss(f[v]);
return f[v];
}
}
int merge(int v,int u)//并查集合并两子集合的函数
{
int t1,t2;
t1=getboss(v);
t2=getboss(u);
if(t1!=t2)
{
f[t2]=t1;
return 1;
}
return 0;
}
int main()
{
int i,temp=0;
int sum=0;
scanf("%d%d",&n,&m);//n表示顶点数,m为边的条数。
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
}
sort(e+1,e+1+m,cmp);//排序
init();
for(i=1;i<=m;i++)
{
if(merge(e[i].u,e[i].v))//看是否形成环
{
temp++;//连通这条边
sum+=e[i].w;
}
if(temp==n-1)//选了n-1条边了,已经形成树了,接下来没必要进行判断了
break;
}
printf("%d\n",sum);
return 0;
}
/*
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
19
*/
二.Prim算法
我们还是用Kruskal的那个图来讲解Prim算法
因为最小生成树是连通的,可以从一个任意一个点到其他任何点。
所以,我们先任意找一个点,我们选择1这个点,比较符合我们的认知,因为啥事儿都从1开始嘛。
找到1这个点,然后在连通这个点的其他点中找最小的那一条,即 1和2 与 1和3 ,很明显是1到2的那条边,把这两个点连接
然后,继续在连接1,2的线中找权值最小的,即在 1和3 2和3 2和4 中挑最小的那一个,很明显是1和3,连接这两个点。
继续重复上述操作,在连接1,3,2的线中寻找最小的线,我们注意到这些线里2和3的权值最小,但是2这个点已经被连接了,所以,不能找已经连接好的点的权边,这时就应该连接3和4
重复上述操作,如下所示
这就是Prim算法的描述,接下来是代码的实现:
# include <iostream>
# include <cstdio>
# define inf 0x3f3f3f
using namespace std;
int main()
{
int n,m;//n表示顶点数,m表示边数
int i,j,k;
int min;
int t1,t2,t3;
int e[110][110];//记录这个图的状态
int dis[110];//dis数组的使用是这个算法的精髓所在
//但dis数组的用法和最短路中的不同,Dijkstra算法中的dis是用来记录单源的最小值,即从顶点1到其他边的最小值
//而在Prim算法中dis数组记录的是已经连接的所有点到其他点的最小值
int b[110];//记录这个点有没有用过
int sum=0,temp=0;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(i==j) e[i][j]=0;
else e[i][j]=inf;
}
}
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
e[t1][t2]=t3;
e[t2][t1]=t3;//注意这时无向图
}
for(i=1;i<=n;i++)
dis[i]=e[1][i];//我们这是选择1为第一个点
b[1]=1;//记录1这个点已经使用
temp++;//用了一个点了
while(temp<n)//如果temp=n则证明所有点已经连接,可以推出循环了
{
min=inf;
for(i=1;i<=n;i++)
{
if(b[i]==0&&dis[i]<min)//找到最小的那个值
{
min=dis[i];//更新最小值
j=i;//记录点
}
}
b[j]=1;//说明该点已经连通
temp++;
sum+=dis[j];
for(k=1;k<=n;k++)
{
if(b[k]==0&&dis[k]>e[j][k])//更新已经连通的点到其他点的最小值
dis[k]=e[j][k];
}
}
printf("%d\n",sum);
return 0;
}
/*
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
19
*/