下面介绍两种求最小生成树算法
1.Kruskal算法
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
1. 把图中的所有边按代价从小到大排序;
2. 把图中的n个顶点看成独立的n棵树组成的森林;
3. 按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。
例题:洛谷P1195
题目背景
小杉坐在教室里,透过口袋一样的窗户看口袋一样的天空。
有很多云飘在那里,看起来很漂亮,小杉想摘下那样美的几朵云,做成棉花糖。
题目描述
给你云朵的个数NNN,再给你MMM个关系,表示哪些云朵可以连在一起。
现在小杉要把所有云朵连成KKK个棉花糖,一个棉花糖最少要用掉一朵云,小杉想知道他怎么连,花费的代价最小。
输入格式
每组测试数据的
第一行有三个数N,M,K(1≤N≤1000,1≤M≤10000,1≤K≤10)N,M,K(1 \le N \le 1000,1 \le M \le 10000,1 \le K \le 10)N,M,K(1≤N≤1000,1≤M≤10000,1≤K≤10)
接下来MMM行每行三个数X,Y,LX,Y,LX,Y,L,表示XXX云和YYY云可以通过LLL的代价连在一起。(1≤X,Y≤N,0≤L<10000)(1 \le X,Y \le N,0 \le L<10000)(1≤X,Y≤N,0≤L<10000)
30%30\%30%的数据N≤100,M≤1000N \le 100,M \le 1000N≤100,M≤1000
输出格式
对每组数据输出一行,仅有一个整数,表示最小的代价。
如果怎么连都连不出KKK个棉花糖,请输出'No Answer'。
输入输出样例
输入 #1
3 1 2
1 2 1
输出 #1
1
代码:
#include<bits/stdc++.h>
using namespace std;
const int num=200005;
struct data{ //结构体存图,start到end需要花费value
int start,end,value;
}a[num];
int f[num]; //并查集数组
bool cmp(data a,data b){ //按费用从低到高排序
return a.value<b.value;
}
int find(int a){ //并查集,用于判断是否成环
if(f[a]==a){
return a;
}
else{
return f[a]=find(f[a]);
}
}
int main(){
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
f[i]=i;
}
for(int i=1;i<=m;i++){
cin>>a[i].start>>a[i].end>>a[i].value;
}
sort(a+1,a+m+1,cmp);
int cnt=0,sum=0;
for(int i=1;i<=m;i++){
if(find(a[i].start)!=find(a[i].end)){ //父节点不一样证明连接起来不会成环
f[find(a[i].start)]=find(a[i].end); //把这两个点归为一个父节点
sum+=a[i].value;
cnt++;
}
if(cnt>=n-k) break; //已经连接了n-k条边,组成k个树
}
if(cnt>=n-k){
cout<<sum<<endl;
}
else cout<<"No Answer"<<endl;
return 0;
}
2.Prim算法
此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。
- 图的所有顶点集合为VV;初始令集合u={s},v=V−uu={s},v=V−u;
- 在两个集合u,vu,v能够组成的边中,选择一条代价最小的边(u0,v0)(u0,v0),加入到最小生成树中,并把v0v0并入到集合u中。
- 重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。
由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息;
struct{
char vertexData //表示u中顶点信息
UINT lowestcost //最小代价
}closedge[vexCounts]
然后存一个自己写的Prim代码:
#include<bits/stdc++.h>
using namespace std;
const int MAX=0x3f3f3f3f;
int edge[1111][1111]; //用来存放两点间的权值
int lowcost[200005]; //用来存放每个点距离Vnew的最短距离
int vis[200005]; //标记,如果为0则是V集合的点,为1则是Vnew集合里的点
int main() {
int n,m,sum=0; //n个点,m条边,sum为最小生成树权值总和
memset(edge,MAX,sizeof(edge));
cin>>n>>m;
for(int i=1; i<=m; i++) {
int a,b,c;
cin>>a>>b>>c;
edge[a][b]=c;
edge[b][a]=c;
}
for(int i=1; i<=n; i++) { //我们把1点当作起始点,更新起始点到每个点的权值
lowcost[i]=edge[1][i];
}
vis[1]=1; //把1点放入Vnew集合,此时Vnew={1},V={其他点}
for(int i=1; i<n; i++) {
int MIN=MAX;
int v=0; //用来标记每次筛选出距离集合Vnew里最近的点
for(int j=1; j<=n; j++) {
if(!vis[j]&&lowcost[j]<MIN) {
MIN=lowcost[j];
v=j;
}
}
if(v) {
vis[v]=1; //因为把这个点v放入了Vnew,所以vis[v]=1
sum+=lowcost[v]; //加上该点构成树的权值
for(int j=1; j<=n; j++) { //依次将V集合里的点的距离Vnew集合的最小权值
if(!vis[j]&&edge[v][j]<lowcost[j]) {
lowcost[j]=edge[v][j];
}
}
}
}
cout<<sum<<endl;
return 0;
}