您的位置:首页 > 其它

最小生成树之 prim & kruskal

2015-10-25 11:29 281 查看
最小生成树:

给定一个带权的 n 个结点的无向连通图,选取一个包含原图中的所有 n 个结点,并且保持图连通的,所有边的权值和最小。

求最小生成树的算法:

a:Kruskal 算法

图的存贮结构采用边集数组,该方法是按边进行连通的,类似一个开始为空的边集,每次选出不在集合中的权重最小的边,判断是否构成回路,若无,将该边加入到边集中,权值相等的边在数组中排列次序可以是任意的,该方法对于边相对比较多的不太实用,会浪费时间。

b:prim 算法

图的存贮结构采用邻接矩阵。该方法是按各个顶点连通的步骤进行的,需要一个开始为空集的顶点集,之后将已连通的顶点陆续加入到集合中,直到顶点集中中包含了所有顶点,如此得到所需的最小生成树。

Kruskal 算法:

方法:将图(n 个结点)中的所有边按权值从小到大排序,依次拿出一条边,若选边后不形成回路则选择该边,否则除去该边,依次选择(n -1)条边,即得最小生成树。



第一步,选择第一条边(权值为1)



第二步:选第二条边(权值为2)



第三步:选第三条边(权值为3)



第四步:选第四条边(权值为4)





第五步:选第五条边(权值为5)(但是要选择当前未连通的,例如3和4的权值也为5但此时3和4 已连通所以就不要选择这条边,而选择2和3





#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 110
using namespace std;
int pre
={0};
struct node
{
int r1,r2;
int p;
}road
;
int cmp(node a,node b)
{
return a.p<b.p;
}
int find(int x)
{
return x==pre[x]?x:pre[x]=find(pre[x]);
}
int main()
{
int n,m,f1,f2,pm,i,flag;
while(scanf("%d%d",&n,&m)&&n)
{
pm=flag=0;
for(i=1;i<=m;++i)pre[i]=i;
for(i=0;i<n;++i)
{
scanf("%d%d%d",&road[i].r1,&road[i].r2,&road[i].p);
}
//Kruskal算法
sort(road,road+n,cmp);//排序
for(i=0;i<n;++i)//选边
{
f1=find(road[i].r1);
f2=find(road[i].r2);
if(f1!=f2)//若无构成回路则选择该边
{
pre[f1]=f2;pm+=road[i].p;//pm为最小生成树的最小权值总和
}
}
for(i=1;i<=m;++i)//判断此时是否为一个合格的最小生成树,即是否连通
{
if(i==find(i))flag++;
}
if(flag>1)printf("?\n");
else printf("%d\n",pm);
}
return 0;
}


Prim 算法:

方法:从指定顶点开始将它加入集合中,然后将集合内的顶点与集合外的顶点所构成的边中选取权值最小的一条边作为生成树的边,并将集合外的那个顶点加入到集合中

,表示该顶点已连通,然后继续通过此方式选点,直到所有点都加入都集合中,即得最小生成树。

例在下图中从1点出发求最小生成树





先写出其邻接矩阵





第一步:从1开始,1进集合,找与集合外所有点所构成的边中权值最小的边

(1,2)-6 (1,4)-5 (1,3)-1

所以取(1,3)这条边

第二步:3进集合,1,3与2,4,5,6构成的最小边为(1,4)-5 (3,6)-4

所以取(3,6)边

第三步:6进集合,1,3,6与2,4,5构成的各最小边(1,2)-6 (3,2)-5 (6,4)-2

所以取(6,4)边

第四步:4进集合,1,3,6,4与2,5构成的各最小边(1,2)-6 (3,2)-5 (6,5)-6

所以取(3,2)边

第五步:2进集合,1,3,6,2,4与5构成的各最小边(2,5)-3

取(2,5)边





当然这里程序实现的时候要有更新最小权值的步骤

#include<cstdio>
#include<cstring>
#define inf 0x3f3f3f3f
using namespace std;
int m,map[110][110];
int vis[110],rec[110];
void prim()
{
int mark,minw,cnt=0,fe=0;
vis[1]=1;
for(int i=1;i<=m;++i)
rec[i]=map[1][i];//记录其他点与指定顶点的边的权值
while(1)
{
minw=inf;
mark=0;
for(int i=1;i<=m;++i)
{
if(!vis[i]&&rec[i]<minw)//未进入集合中且当前权值最小
{
minw=rec[i];
mark=i;
}
}
if(!mark)//若再找不到这样条件的边即退出循环
break;
fe+=minw;//权值总和
cnt++;//所选顶点的个数
vis[mark]=1;
for(int i=1;i<=m;++i)
{
if(!vis[i]&&rec[i]>map[mark][i])//更新未进入集合的点与集合中的点的最小权值(这里不明白的话要动手自己模拟画一下
rec[i]=map[mark][i];
}
}
if(cnt!=m-1)//若不包括所有顶点则不是最小生成树
printf("?\n");
else
printf("%d\n",fe);
}
int main()
{
int n,a,b,f;
while(scanf("%d%d",&n,&m)&&n)
{
memset(vis,0,sizeof(vis));
memset(rec,0,sizeof(rec));
memset(map,inf,sizeof(map));
for(int i=0;i<n;++i)
{
scanf("%d%d%d",&a,&b,&f);
if(f<map[a][b])//邻接矩阵
map[a][b]=map[b][a]=f;
}
prim();
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: