最小生成树之lazy prim算法

3 篇文章 0 订阅
2 篇文章 0 订阅

定义:在一给定的无向连通图G = (V, E) 中,找到一颗生成树,使得这V-1条边的权值之和最小,这样的生成树就是最小生成树。
而所谓的生成树,简单的说就是在所有的边之间找到V-1条边,使得所有顶点连通且没有形成环。
最小生成树其实是最小权重生成树的简称。
那么如何找到这样的最小生成树呢?这里,就可以用prim算法。这里,我们先介绍lazy prim算法。
在介绍lazy prim算法之前,我们先来介绍一个重要的定理——切分定理。它是prim算法的核心。
我们以一个实例作为讲解。
在这里插入图片描述
这样的一个图表示的各个边的权值如下:
在这里插入图片描述
好了,到底什么是切分定理呢?这里我们先介绍几个定义:
切分
把图中的节点分为两部分,称为一个切分(Cut)
横切边
如果一个边的两个端点,属于切分(Cut)不同的两边,这个边称为横切边(Crossing Edge);
切分定理
给定任意切分,横切边中权值最小的边必然属于最小生成树。
如果文字描述不是太理解,小伙伴们可以看下面这张图。
在这里插入图片描述
我们看到这个图有两个部分,蓝色部分和红色部分。而所有绿色的边,就是所谓的横切边,因为绿色的边的两个端点属于属于不同的部分。
那么根据切分定理,我们知道0~7的这条边就是最小生成树的一条边,因为其权值最小。
那么问题来了,为什么这条边属于最小生成树?
这里我们给出证明:
在这里插入图片描述
我们看上面这个图,我们可以把这个图分为两个部分。由于图的连通性,那么在这两部分中必然有横切边,并且我们假定绿色的边权值最小。因为要保持连通性,在这三条边中必然要选择一条边使得这两部分连通。如果我们假定左右两部分都已经是最小生成树了,那么从三条边中选择,为了保持总权值最小,必然是选择绿色的边。这就是切分定理。
如果按照这个定理,聪明的小伙伴们可能对这个lazy prim算法有了眉目。既然给定任意切分,横切边中权值最小的边必然属于最小生成树,那么我们完全可以从第0个顶点开始,第0个顶点与其他部分切分,找到最小权值边,然后向下一个顶点扩散。再把这个顶点与第0个顶点当作同一部分与剩下的顶点切分,再找到下一个权值最小边,依此类推。当然,这里我们需要注意不能让这个树变成了环。也就是同一部分的顶点之间的边不能选择。
下面我们用几个图解释一下。
将 0 作为起始点,开始切分,逐步将蓝色阵营的顶点 转换到红色阵营中 。
(1)将 0 加入到红色阵营中
在这里插入图片描述
这样一来,就形成了一个切分,相应的就有横切边
接下来将最小堆作为辅助数据结构,可以非常快速地找到横切边中权值最小的那条边。将横切边放入到最小堆中,并拿出最小堆中权值最小的边 。此时,0-7 是权值最小的边,权值为 0.16。
(2)将 7 加入到红色阵营中
在这里插入图片描述
这样一来,就形成了一个新的切分,相应的就有新的横切边。
将新的横切边放入到最小堆中,并拿出最小堆中权值最小的边 。
此时,1-7 是权值最小的边,权值为 0.19。
(3)将 1 加入到红色阵营中
在这里插入图片描述
这样一来,就形成了一个新的切分,相应的就有新的横切边。将新的横切边放入到最小堆中,并拿出最小堆中权值最小的边。
此时,0-2 是权值最小的边,权值为 0.26。
(4)将 2 加入到红色阵营中
在这里插入图片描述
这样一来,就形成了一个新的切分,相应的就有新的横切边。将新的横切边放入到最小堆中,并拿出最小堆中权值最小的边 。
此时,2-3 是权值最小的边,权值为 0.17。
注意:此时,1-2 和 2-7 实际上已经不是横切边了,本来应该从最小堆中剔除,不能再作为最小生成树 的候选边,但这里并不急着剔除,而是依然保留在最小堆中,当二者上移到最小堆的顶端被拿出来时,就会发现二者的两端同属红色阵营,直接把二者扔掉即可。这也正是 Lazy Prim 算法的懒之所在。
(5)将 3 加入到红色阵营中
在这里插入图片描述
这样一来,就形成了一个新的切分,相应的就有新的横切边 。将新的横切边放入到最小堆中,并拿出最小堆中权值最小的边。 此时,5-7 是权值最小的边,权值为 0.28。
(6)将 5 加入到红色阵营中
在这里插入图片描述
这样一来,就形成了一个新的切分,相应的就有新的横切边。将新的横切边放入到最小堆中,并拿出最小堆中权值最小的边。
此时,1-3 是权值最小的边,权值为 0.29,但 1、3
同属红色阵营,不是横切边,直接扔掉。

下一个最小堆中的权值最小的边是 1-5,权值为 0.32,但 1、5 同属红色阵营,不是横切边,直接扔掉。

下一个最小堆中的权值最小的边是 2-7,权值为 0.34,但 2、7 同属红色阵营,不是横切边,直接扔掉。

下一个最小堆中的权值最小的边是 4-5,权值为 0.35,且 4、5 分属不同阵营,一定属于最小生成树 。
(7)将 4 加入到红色阵营中
在这里插入图片描述
这样一来,就形成了一个新的切分,相应的就有新的横切边。将新的横切边放入到最小堆中,并拿出最小堆中权值最小的边。
此时,1-2 是权值最小的边,权值为 0.36,但 1、2
同属红色阵营,不是横切边,直接扔掉。

下一个最小堆中的权值最小的边是 4-7,权值为 0.37,但 4、7 同属红色阵营,不是横切边,直接扔掉。

下一个最小堆中的权值最小的边是 0-4,权值为 0.38,但 0、4 同属红色阵营,不是横切边,直接扔掉。

下一个最小堆中的权值最小的边是 2-6,权值为 0.40,且 2、6 分属不同阵营,一定属于最小生成树。
(8)将 6 加入到红色阵营中
在这里插入图片描述
至此,所有蓝色阵营的顶点都已经转换到红色阵营中,此时,Lazy Prim 算法其实就已经可以结束了。
但如果是以最小堆中的边为空作为结束依据的话,依然可以从最小堆中继续拿出权值最小的边,不过,这之后拿出的边肯定不再是横切边了。

此时,3-6 是权值最小的边,权值为 0.52,但 3、6
同属红色阵营,不是横切边,直接扔掉。

下一个最小堆中的权值最小的边是 0-6,权值为 0.58,但 0、6 同属红色阵营,不是横切边,直接扔掉。

下一个最小堆中的权值最小的边是 4-6,权值为 0.93,但 4、6 同属红色阵营,不是横切边,直接扔掉。
(9)最后
在这里插入图片描述
上图就是最小生成树。
我们来看一下具体的代码实现:

#include<iostream>
#include<queue>
#define INF 1e4
using namespace std;
double G[8][8] =
{
 //0   1   2   3   4   5   6   7 
 {INF,INF,0.26,INF,0.38,INF,0.58,0.16},
 {INF,INF,0.36,0.29,INF,0.32,INF,0.19},
 {0.26,0.36,INF,0.17,INF,INF,0.40,0.34},
 {INF,0.29,0.17,INF,INF,INF,0.52,INF},
 {0.38,INF,INF,INF,INF,0.35,0.93,0.37},
 {INF,0.32,INF,INF,0.35,INF,INF,0.28},
 {0.58,INF,0.40,0.52,0.93,INF,INF,INF},
 {0.16,0.19,0.34,INF,0.37,0.28,INF,INF}
};//数组初始化
//定义一个结构体,表明这是一个由v、u连接的边,权值为w
//并将运算符重载,比较大小的是比较权值
struct Edge {
 int v;
 int u;
 double w;
 bool operator < (const Edge& a) const {
  return w > a.w;
 }
};
//标记数组,判断一个顶点是否已经在红色阵营了
bool marked[8];
//优先队列
priority_queue<Edge,vector<Edge>,less<Edge> > w;
//vis函数是用来把x顶点放入红色阵营
//这里是赋值为1
//并把所有与x顶点连接的边放入优先队列
void vis(int x) {
 marked[x] = 1;
 for (int i = 0; i < 8; i++) {
  if (G[x][i] == INF)
   continue;
  w.push({ x,i,G[x][i] });
 }
}
int main()
{
 //先访问第0个顶点
 vis(0);
 //最小权值
 double minw = 0;
 while (!w.empty()) {
  Edge e = w.top();
  w.pop();
  //如果顶点u和v的都在红色阵营,那么值一定相同,这时我们就跳过
  if (marked[e.u] == marked[e.v])
   continue;
  //访问顶点u
  vis(e.u);
  minw += e.w;
 }
 cout << minw;
 return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值