一个连通图的生成树是一个极小连通子图,其中含有图中的全部顶点和构成一棵树的(n-1)条边。
最小生成树:图中所有生成树中具有边上的权值之和最小的树。
最小生成树是最基本的图论问题之一,可由Kruskal(克鲁斯卡尔)算法和Prim(普里姆)算法求解。
两种算法构造最小生成树的原理不同。
Prim算法核心:从某一顶点出发,每次贪心选择与该点连通且未曾选入的边权最小的顶点加入集合,考虑每次新选入的顶点可能造成的影响,需修改候选边的边权和前驱结点,重复操作,直到选完所有顶点(除初始顶点外,只用选n-1个)。
模板题:http://hihocoder.com/problemset/problem/1097
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005,INF=0x3f3f3f3f;
int n,edge[maxn][maxn];
void init(){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&edge[i][j]);//初始化邻接矩阵
}
}
}
int prim(int v){
int MIN,lowcost[maxn],closest[maxn],k,sum=0;
for(int i=1;i<=n;i++){
lowcost[i]=edge[v][i];
closest[i]=v;
}
for(int i=1;i<n;i++){//贪心选择n-1个顶点加入
MIN=INF;
for(int j=1;j<=n;j++){
if(lowcost[j]&&lowcost[j]<MIN) MIN=lowcost[j],k=j;
}
lowcost[k]=0,sum+=MIN;
for(int j=1;j<=n;j++){//修改侯选边边权和前驱结点
if(lowcost[j]&&edge[k][j]<lowcost[j]) lowcost[j]=edge[k][j],closest[j]=k;
}
}
return sum;
}
int main(){
cin>>n;
init();
cout<<prim(1);
return 0;
}
Kuskal算法核心:与Prim算法相比有一些相似之处,都运用了贪心策略。它是一种按权值的递增次序选择合适的边来构造最小生成树的方法。对图中所有存在的边按递增排序,依次选择,若不能构成回路,表示可以选择,直到选完n-1条边。
并查集:用来判断该边并入后会不会产生环。
模板题:http://hihocoder.com/problemset/problem/1098
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f,maxn=1e6+5;
int n,m,vset[maxn];
struct node{
int u,v,w;
}a[maxn];
bool cmp(node x,node y){
return x.w<y.w;
}
int find(int x){//递归寻找根结点
if(x==vset[x]) return x;
else return vset[x]=find(vset[x]);
}
int kruskal(){
int sum=0,s1,s2;
sort(a,a+m,cmp);
for(int i=0;i<n;i++) vset[i]=i;
for(int i=0;i<m;i++){
s1=find(a[i].u),s2=find(a[i].v);//分别得到两个顶点所属集合编号
if(s1!=s2) vset[s1]=s2,sum+=a[i].w;//不会形成环,可加入
}
return sum;
}
int main(){
cin>>n>>m;
for(int i=0;i<m;i++) scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].w);//初始化边集
cout<<kruskal();
return 0;
}