导入
有这么一群树,他们有一个共同的名字—最小生成树,喜欢捉迷藏的他们经常会伪装成图,以此来混淆你的视听,让你摸不着头脑。那么我们有没有办法使用一个算法来揪出这些藏在图里的最小生成树呢?
什么是最小生成树
要想找出他们,我们必须先理解什么是最小生成树
最小生成树是在一个无向图中,拥有这个图所有的顶点,且所有边都是来自于图的边,并且满足这棵树的边权之和是最小的
最小生成树的性质
那最小生成树们都有什么性质是需要我们注意的呢
1.最小生成树是树,因此其边数是等于顶点数减1,而且树内一定不会有环
2.对于给定的图,其最小生成树可以是不唯一的,但是边权是唯一的
现在我们知道了最小生成树是什么,有什么性质,那么我们究竟该如何在图中找出一个最小生成树呢?
Prim 算法
这就需要今天的主角— Prim算法 的帮助了
算法描述
Prim算法,是运用贪心的策略来求加权连通图的最小生成树
和Dijkstra算法的关系
看到上面的描述后,已经有人坐不住了:这不就是Dijksra算法的翻版吗,连策略都相同。
请注意:虽然实现和策略上Dijksra算法与Prim算法相似,但是二者之间却有一个很大的差异—求的东西不同
Dijkstra算法求得的是图中各顶点到起点的距离(求的点),而Prim算法则求的是图中的最小生成树(求的边)
算法实现
搞清楚这一点之后,我们就来实现一下Prim算法吧!
数据处理
和Dijkstra算法类似,但是没有了记录各顶点到起点距离的数组dist
struct Node{
int from,id,w;//前驱,ID,边权
Node(){
}
Node(int i,int d,int l){
from=i;id=d;w=l;
}
friend bool operator < (Node a,Node b){
return a.w>b.w;
}
};
bool flag[MXN+1];//和Dijkstra算法相似,记录每个顶点是否计算过
priority_queue<Node>pque;
int g[MXN+1][MXN+1];//用于存储边的各个数据(前驱,ID,边权)
内部实现
在进行内部实现之前,我们先来解决几个问题
怎么初始化
回顾一下对于Prim算法进行的数据处理,不难发现,我们应该对起点 flag g这三个数据进行初始化,那么怎么进行呢?
memset(g,0x3f,sizeof(g));//把不存在的边设定长度为无穷大,以此达到过滤的效果
memset(flag,false,sizeof(flag));//所有的点都还没有进行计算
pque.push(Node(-1,sid,0));//起点没有前驱,到起点的距离为零
while循环的结束条件是什么
和Dijkstra算法实现的过程相同,Prim算法同样需要使用while循环,那么,循环结束的条件是什么呢?
回顾一下:Prim算法需要找到的是最小生成树,只要找到最小生成树,就可以结束循环,而最小生成树有一个性质,即其边数是等于顶点数减1,那么只要我们记录下的边的数量等于顶点数减1就可以结束循环了
while(result.size()<n-1)
如何排除起点对统计边造成的影响
初始化后的起点(-1[前驱],1[ID],0[边权]),很容易被当成一条正确的边被记录下来,从而使程序出现错误,那如何解决这一问题呢
我们有两种办法
方案一:判断当前边的ID是否为起点
if(nd.id!=sid)result.push_back(nd);//sid表示起点ID
方案二:判断当前边的前驱是否为-1
if(nd.from!=-1)result.push_back(nd);
这两种办法都可以解决起点干扰我们进行记录的问题
在解决这些小问题后,我们就可以按照这些想法来实现Prim算法了
vector<Node> Prim(int sid){
vector<Node>result;
memset(flag,false,sizeof(flag));
pque.push(Node(-1,sid,0)