最小生成树
在开始本节之前,我们必须先了解几个概念:
极小连通子图: 若一个图是图G的连通子图,该图再删去任意一条边,该图将不再连通,满足这个性质的叫极小连通子图
生成树: 指的是包含图G所有顶点的极小连通子图
最小生成树: 各边权值最小的生成树
求解最小生成树问题的两大常用算法是Kruskal算法和Prim算法
其中Kruskal算法适用于稀疏图,Prim算法适用于稠密图
Kruskal算法
算法步骤:
1.将所有的边按权值大小进行升序排列
2.依次遍历排序好的边的集合,每次遍历到一条边的时候,判断其是否会和已经遍历的边构成环,如果不会,加入结果集合
算法原理: 贪心
难点:如何判断是否构成环?
各位思考完上面的问题,就可以看看下面的代码实现了
代码实现:
例题:给出顶点数n,边数m,要求算出最小生成树的路径长度
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
struct node{ //定义边的结构体,包含顶点和权值
int x,y,z;
bool operator<(const node& b)const{
return z<b.z;
}
}edge[N];
int fa[N],n,m,ans;
int find(int x){
if(x==fa[x])return x;
return fa[x]=find(fa[x]);
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>edge[i].x>>edge[i].y>>edge[i].z;
}
sort(edge+1,edge+1+m);
for(int i=1;i<=n;i++){
fa[i]=1;
}
for(int i=1;i<=m;i++){
int x=find(edge[i].x);
int y=find(edge[i].y);
if(x==y)continue;
fa[x]=y;
ans+=edge[i].z;
}
cout<<ans<<endl;
return 0;
}
笔者判断构成环的办法是:并查集
Prim算法
算法步骤:
仍然采用贪心策略,每次我们都选择于所选点连接后权值最小的边,有些类似Dijkstra算法
不优化的话时间复杂度是O(n^2),使用优先队列优化是O(nlogn)
代码实现:
例题:沿用上一题
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
typedef pair<int,int> pii;
int ver[N],ne[N],edge[N],head[N],tot;
int n,m,ans;
int dis[N];//dis[i]就表示第i的点与已选集合的最短距离
int vis[N],cnt;
void add(int u,int v,int val){
ver[++tot]=v; //这里表示当前tot值代表的顶点
edge[tot]=val; //这里设定权值
ne[tot]=head[u]; //这里以及下面相当于链表的头插法
head[u]=tot;
}
priority_queue<pii,vector<pii>,greater<pii> >q;
void prim(){
dis[1]=0; //pair这里存储的第一个分量是已经选取的点于其余各点最小权值
q.push({0,1});
while(!q.empty()&&cnt!=n){
int d=q.top().first;
int u=q.top().second;
q.pop();
if(vis[u])continue;
cnt++;
ans+=d;
vis[u]=1;
for(int i=head[u];i;i=ne[i]){
int v=ver[i];
if(edge[i]<dis[v]){
dis[v]=edge[i];
q.push({dis[v],v});
}
}
}
}
int main(){
memset(dis,0x3f3f3f3f,sizeof dis);
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
prim();
cout<<ans<<endl;
return 0;
}
各位可以画图来模拟一下上面的过程就会比较好理解,如下图: