大话数据结构---(六)最小生成树

1.最小生成树

我们在讲图的定义和术语时,曾经提到过,一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。那么我们把构造连通网的最小代价生成树称为最小生成树。在了解最小生成树的概念之后,我们更应该看重的是如何获取最小生成树,并如何利用最小生成树来解决问题。

2.普利姆算法(Prim)

在给出Prim算法代码之前,我们先来演示一个实例,看看普利姆算法是如何一步步获取最小生成树的,如图1所示。 在这里插入图片描述我们先来看下演示图,再和大家逐步分析:
在这里插入图片描述
主要思想,在当前可选路中选出最短的那条路。我们的目的是在不成环的前提下,连通所有结点,而不是一步遍历所有结点,所以当我们获取下一条路径时,我们的出发点可以是已走路径上的任意一点,下面给出最小生成树的生成步骤。

  • 第一步:假设从V0点开始,与V0点相连的边有V0->V1=10,V0->V5=11,我们选择最短的那条边,即选择V0->V1。
  • 第二步:这时我们的路径上有V0、V1两个顶点,与已走路径相连的边有V0->V5=11、V1->V6=16、V1->V8=12、V1->V2=18,我们选择最短的那条边,即选择V0->V5。
  • 第三步:这时我们的路径上有V0、V1、V5三个顶点,与已走路径相连的边有V5->V6=17、V5->V4=26、V1->V2=18、V1->V8=12、V1->V6=16,我们选择最短的那条边,即选择V1->V8。
  • 第四步:这时我们的路径上有V0、V1、V5、V8四个顶点,与已走路径相连的边有V5->V6=17、V5->V4=26、V1->V2=18、V1->V6=16、V8->V2=8、V8->V3=21,我们选择最短的那条边,即选择V8->V2。
  • 第五步:这时我们的路径上有V0、V1、V5、V8、V2五个顶点,与已走路径相连的边有V5->V6=17、V5->V4=26、V1->V2=18、V1->V6=16、V8->V3=21,V2->V3=22,我们选择最短的那条边,即选择V1->V6。
  • 第六步:这时我们的路径上有V0、V1、V5、V8、V2、V6六个顶点,与已走路径相连的边有V5->V6=17、V5->V4=26、V1->V2=18、V8->V3=21,V2->V3=22,V6->V3=24、V6->V7=19我们选择最短的那条边,我们选择最短的那条边,即选择V6->V5,但是我们发现,如果选这条边那么已走路径V0、V1、V6、V5构成的路径成环,所以排除该边,在剩余边中找最短边,即选择V6->V7。
  • 第七步:这时我们的路径上有V0、V1、V5、V8、V2、V6、V7七个顶点,与已走路径相连的边有V5->V6=17、V5->V4=26、V1->V2=18、V8->V3=21,V2->V3=22,V6->V3=24、V7->V3=16、V7->V4=7,我们选择最短的那条边,即选择V7->V4。
  • 第八步:这时我们的路径上有V0、V1、V5、V8、V2、V6、V7、V4八个顶点,与已走路径相连的边有V5->V6=17、V5->V4=26、V1->V2=18、V8->V3=21,V2->V3=22,V6->V3=24、V7->V3=16、V4->V3=20我们选择最短的那条边,即选择V7->V3。
  • 第九步:这时我们的路径上有V0、V1、V5、V8、V2、V6、V7、V4、V3九个顶点,所有顶点连接完成,结束。
    现在我们来分析如何用代码来实现上述过程,这里我们用邻接矩阵来存储图中信息。为了防止重复经过某个点,所以我们定义一个类似visited数组。除此之外,还要存储与已走路径连接的边值,选出最小边,将最小边另一端的点加入已走路径中。大致思路是这样,下面给出普利姆算法代码,并加以说明。
//普利姆算法生成最小生成树
void MiniSpanTree_Prim(MGraph G)
{
   int min,i,j,k;
   int adjvex[MAXVEX];//保存相关顶点下标
   int lowcost[MAXVEX];//保存相关顶点间边的权值
   lowcost[0]=0;//初始化第一个权值为0,即V0加入生成树
                //lowcost的值为0,在这里就是此下标的顶点已经加入生成树
   adjvex[0]=0;//初始化第一个顶点下标为0
   for(i=1;i<G.numVertexes;i++)//循环除下标为0外的全部顶点
   {
     lowcost[i]=G.arc[0][i];//将V0顶点与之有边的权值存入数组
     adjvex[i]=0;//初始化都为v0的下标
   }
   for(i=1;i<G.numVertexes;i++)
   {
     min=INFINITY;//初始化最小权值为无穷大
                  //通常设置为不可能的大数字如32767,65535等
     j=1;k=0;
     while(j<G.numVertexes)
     {
       if(lowcost[j]!=0&&lowcost[j]<min)
       {
         //如果权值不为0,且权值小于min
         min=lowcost[j];//则让当前权值成为最小值
         k=j;//将当前最小值的下标存入k
       }
       j++;
     }
     cout<<adjvex[k]<<","<<k<<endl;//打印当前顶点边中权值最小边
     lowcost[k]=0;//将当前顶点的权值设置为0,表示此顶点已经完成任务
     for(j=1;j<G.numVertexes;j++)
     {
       if(lowcost[j]!=0&&G.arc[k][j]<lowcost[j])
       {
         //若下标为k点点各边权值小于此前这些顶点未被加入生成树权值
         lowcost[j]=G.arc[k][j];//将较小权值存入lowcost
         adjvex[j]=k;//将下标为k的顶点存入adjvex
       }
     }
   }
}

3.克鲁斯卡尔算法(Kruskal)

现在我们来换一种思考方式,普利姆算法是以某顶点为起点,逐步找顶点上最小权值的边来构建最小生成树的。而克鲁斯卡尔算法直接以边为目标去构建,因为权值是在边上,直接去找最小权值的边构建生成树也是很自然的想法,只不过构建时要考虑是否形成环路而已。此时我们就用到了图的存储结构中的边集数组结构。以下是edge边集数组结构的定义代码:

//对边集数组Edge结构的定义
typedef struct{
  int begin;
  int end;
  int weight;
}Edge;

我们将图2转化为图3的边集数组,并且对它们按权值从小到大排序。
在这里插入图片描述在这里插入图片描述克鲁斯卡尔算法如果通俗点介绍最小生成树的生成过程的话,就是:肉眼可见的依次挑最小边,如果成环,则跳过该边,直到最后所有点连接完毕,下面给出算法代码。

//Kruskal算法生成最小生成树
void MiniSpanTree_Kruskal(MGraph G)//生成最小生成树
{
 int i,n,m;
 Edge edges[MAXEDGE];//定义边集数组
 int parent[MAXVEX];//定义一数组用来判断边与边是否形成环路
 //此处省略将邻接矩阵G转化为边集数组edges并按权由小到大排序的代码
 for(i=0;i<G.numVertexes;i++)
 {
   parent[i]=0;//初始化数组值为0
 }
 for(i=0;i<G.numEdges[i];i++)
 {
   n=Find(parent,edges[i].begin);
   m=Find(parent,edges[i].end);
   if(n!=m)//假如n与m不等,说明此边没有与现有生成树形成环路
   {
    parent[n]=m;//将次边的结尾顶点放入下标为起点的parent中,表示此顶点已经在生成树集合中
    cout<<edges[i].begin<<","<<edges[i].end<<":"<<edges[i].weight<<endl;
   }
 }
}
int Find(int *parent,int f)//查找连线顶点的尾部下标
{
  while(parent[f]>0)
    f=parent[f];
    return f;
}

4.总结

对比两个算法,克鲁斯卡尔算法主要是针对边来展开,边数少时效率会非常高,所以对于稀疏图有很大的优势;而普利姆算法对于稠密图,即边数非常多的情况会更好一些。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路的苟狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值