最小生成树
所谓最小生成树就是在一个无向连通图中找到一棵树,保证这棵树的边权和最短
Kruskal算法
基本步骤:
- 将所有边按边权排序
- 每一次取当前状态下未加入答案的最小边,判断这条最小边是否能使两个未连通的连通块连通,若能连通,就将这个边加入答案
- 重复上一步骤,直至边数为n-1条或点数为n个(n为图中顶点数)
如图,是一张有五个顶点的无向连通图。
如图,取当前边权最小的边E(1,2) ,进行判断。由于其连接了1和2这两个未连通的顶点,所以这条边可被加入答案。至此,顶点1和顶点2成为一新连通分量 (注:以下所说的连通都是对于红色的边来说的)
重复上述判断过程加入E(3,5),该边连接了3和5两个未连接的连通分量
重复上述判断过程加入E(2,3),该边连接了1,2和3,5两个未连接的连通分量
重复上述判断过程,先取当前未加入答案的最小边E(2,5),但是它所连的边在同一个连通分量中,所以这条边并不是我们生成树所必要的边,我们不去选择这条边。再次重复上述过程加入E(1,4),该边连接了1,2,3,5和4两个未连接的连通分量。至此,该图中所有顶点已全部连通 ,最小生成树已生成完毕
算法实现:
首先,将边存起来,按边权从小到大排序,按顺序取边
在对于一条边所连的两个顶点是不是在同一个连通分量中时,需要使用并查集
代码如下:
洛谷P3366最小生成树【模板】
输入格式:
第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)
接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi
输出格式:
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
struct Qw
{
int x,y,z;
}k[500005];
int fa[5005];
bool cmp(Qw a,Qw b)
{
return a.z<b.z;
}
int find(int q)//并查集的查找+路径压缩
{
if(q==fa[q]) return q;
int re=find(fa[q]);
fa[q]=re;
return re;
}
int main()
{
int n,m; scanf("%d%d",&n,&m);
int p=0;
for(int i=1;i<=m;i++)
{
int xx,yy,zz; scanf("%d%d%d",&xx,&yy,&zz);
p++;
k[p].x=xx; k[p].y=yy; k[p].z=zz;
}
sort(k+1,k+p+1,cmp);//将边排序
for(int i=1;i<=n;i++) fa[i]=i;
int ans=0,v=0;//v为所连边数
for(int i=1;i<=p;i++)
{
if(v==n-1) break;//当边数已达到n-1时,最小生成树就已生成完毕
if( find(k[i].x)!=find(k[i].y) ) //判断这条边的两顶点是否在同一连通分量里
{
fa[ find(k[i].y) ]=find(k[i].x);//若是,就将这两条边的顶点相连,使其成为同一连通分量
ans=ans+k[i].z; v++; //将这条边加入答案
}
}
printf("%d",ans);
return 0;//尴尬,并没有判断"orz"的情况,dfs判断即可
}
Prim算法
基本步骤:
从图中的任意一点出发,将它自己当做一连通分量,寻找其它未在该连通分量中的点与该连通分量相连的边中边权最小的边,并将其加入答案,形成一新的连通分量,重复上一步骤,直至边数为n-1条或点数为n个(n为图中顶点数)
如图,是一张有五个顶点的无向连通图。
如图,选择一与1相连的边中边权最短的边E(1,2),1,2即成为一个连通分量(注:以下所说的连通都是对于红色的边来说的)
如图,选择一与1,2连通分量相连的边中边权最短的边E(2,3),1,2,3即成为一个连通分量
如图,选择一与1,2,3连通分量相连的边中边权最短的边E(3,5),1,2,3,5即成为一个连通分量
如图,选择一与1,2,3,5连通分量相连的边中边权最短的边E(1,4),1,2,3,4,5即成为一个连通分量,最小生成树生成完毕
算法实现:
从一个顶点开始,每选择一条边后,用连通分量的新加入的点更新其余未加入的点到连通分量的最短距离即可
在选择边时应选择距离该连通分量最近的边,此过程可用优先队列优化
代码如下:
洛谷P3366最小生成树【模板】
输入格式:
第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)
接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi
输出格式:
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
int fir[500005];
int nxt[500005];
int tto[500005];
int v[500005];
int b[5005]={0};//标记该点是否在连通分量中
int dist[5005];//距离连通分量的最小值
int p=0;
void add(int x,int y,int z)
{
p++;
nxt[p]=fir[x];
fir[x]=p;
tto[p]=y;
v[p]=z;
return;
};
priority_queue<pair<int,int> >q;
int main()
{
int n,m; scanf("%d%d",&n,&m);
memset(fir,-1,sizeof(fir));//建图
memset(nxt,-1,sizeof(nxt));
for(int i=1;i<=m;i++)
{
int x,y,z; scanf("%d%d%d",&x,&y,&z);
add(x,y,z); add(y,x,z);
}
for(int i=1;i<=n;i++) dist[i]=2147483647;
int p=1; dist[1]=0;
q.push( make_pair(-dist[1],1) );//从第一个点开始生成
int ans=0,kv=0;//kv为已在连通分量中的点数
while(!q.empty() )
{
if(kv==n) break;//如果点数为n,则最小生成树生成完毕
int x=q.top().second; q.pop();
if(b[x]==1) continue; b[x]=1; kv++; //向连通分量中加入一个点
ans=ans+dist[x]; //将该边加入答案
for(int j=fir[x];j!=-1;j=nxt[j])//更新其余未加入的点到连通分量的最短距离
{
if(v[j]<dist[tto[j]] && b[tto[j]]==0)
{
//若更新的边权比原边权小,并且这点没在连通分量中,则将这点加入优先队列中
dist[ tto[j] ]=v[j];
q.push(make_pair(-dist[tto[j]],tto[j]));
//存入边权的相反数,这样小的数就会变成大的,从而实现优先队列中的元素从小到
//大排列
}
}
}
printf("%d",ans);
return 0;//尴尬,同样没有判断"orz"的情况,dfs判断即可
}