让我们先了解一下什么是最小生成树,就是在一个图里找到一颗树的权值和最小。
一.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 */