用prim和kruskal算法求最小生成树问题(基础)
kruskal是常用的
prim有朴素版本和 堆优化版本(基本不用) 用邻接矩阵
如果是稀疏图就用kruskal O( mlogn ) 直接用三元组存边
kruskal:基于并查集
1.先把所有边排序
2.然后从小到大枚举所有边
3.如果说当前这条边的两个点连通了 那么就不操作 如果当前的两个点所在的连通块不连通 那么就加到最小生成树上
prim步骤:
原理:从某一个点开始逐渐把所有点和这个点连通 每一次连通选着当前这个点所在连通块和外面连的所有边 从当前所有和外界里面选一条最短的边就加到连通块去
两个算法的证明
1. 最短网络
裸题
代码
#include <bits/stdc++.h>
using namespace std;
const int N=110;
int n;
int w[N][N];
int dis[N];
bool st[N];
int prim()
{
int res=0;
memset(dis,0x3f,sizeof dis);
dis[1]=0;
for(int i=0;i<n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
{
if(!st[j]&&(t==-1||dis[t]>dis[j]))
t=j;
}
res+=dis[t];
st[t]=true;
for(int j=1;j<=n;j++) dis[j]=min(dis[j],w[t][j]);
}
return res;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
cin>>w[i][j];
}
cout<<prim()<<endl;
return 0;
}
2.局域网
这题是早这个图的每个连通块内(一开始所有计算机不是连通的,是有一定的连通块),求一颗生成树,相当于求“生成森林” 那么这题就是求每一个连通块内的最小生成树
做kruskal算法
1.将所有边从小到大排序
2.依次枚举每条边的a b w 如果a b 不连通,那么就将当前边加到最小生成树去
#include <bits/stdc++.h>
using namespace std;
const int N=110,M=220;
struct node
{
int a,b,w;
bool operator< (const node &t)const
{
return w<t.w;
}
}e[M];
int p[N];
int n,m;
int find(int x)
{
if(p[x]!=x) return p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) p[i]=i;
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
e[i]={a,b,c};
}
sort(e,e+m);
int res=0;
for(int i=0;i<m;i++)
{
int a=find(e[i].a),b=find(e[i].b);
if(a!=b) p[a]=b;
else res+=e[i].w;
}
cout<<res<<endl;
return 0;
}
3.繁忙的都市
本题的最小生成树:最大的边权最小
做法
1.将所有边从小到大排序
2.从小到大枚举每条边 a,b,w
如果已经连通 pass
如果没连通 那么就将当前边选出来
就是kruskal
#include <bits/stdc++.h>
using namespace std;
const int N=310,M=10010;
struct node
{
int a,b,w;
bool operator< (const node &t) const
{
return w<t.w;
}
}e[M];
int p[N];
int n,m;
int find(int x)
{
if(p[x]!=x) return p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) p[i]=i;
for(int i=0;i<m;i++)
{
int a,b,w;
cin>>a>>b>>w;
e[i]={a,b,w};
}
sort(e,e+m);
int res=0;
for(int i=0;i<m;i++)
{
int a=find(e[i].a),b=find(e[i].b);
int w=e[i].w;
if(a!=b)
{
p[a]=b;
res=w;
}
}
cout<<n-1<<" "<<res<<endl;
return 0;
}
4.联络员
先把必选的全选上 再做kruskal
#include <bits/stdc++.h>
using namespace std;
const int N=2010,M=10010;
int n,m;
struct node
{
int a,b,w;
bool operator< (const node &t)const
{
return w<t.w;
}
}e[M];
int p[N];
int find(int x)
{
if(p[x]!=x) return p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
p[i]=i;
int res=0,k=0;
for(int i=0;i<m;i++)
{
int t,a,b,w;
cin>>t>>a>>b>>w;
if(t==1)
{
res+=w;
p[find(a)]=find(b);
}
else e[k++]={a,b,w};
}
sort(e,e+k);
for(int i=0;i<k;i++)
{
int a=find(e[i].a),b=find(e[i].b),w=e[i].w;
if(a!=b)
{
p[a]=b;
res+=w;
}
}
cout<<res<<endl;
return 0;
}
5.连接格点
和上题思路一样
kruskal要排序 但是这题边权只有1 2所以我们先把边权为1的全建出来 再把边权为2的点建出来 所以就省了排序
#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=N*N,K=2*M;
int ids[N][N];//用来将坐标映射到点
int n,m,k;
struct Edge
{
int a,b,w;
}e[K];
int p[M];
int find(int x)//并查集
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void edge()
{
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1},dw[4]={1,2,1,2};//四个方向
for(int s=0;s<2;s++)//s表示余数,0表示打横走,1表示纵着走
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int u=0;u<4;u++)
if(u%2==s)//走的途径
{
int x=i+dx[u],y=j+dy[u],w=dw[u];
if(x<=0||x>n||y<=0||y>m) continue;//假如越界
int a=ids[i][j],b=ids[x][y];//获取当前位置
if(a<b) e[k++]={a,b,w};//为了避免重复假如
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n*m;i++) p[i]=i;//初始化
for(int i=1,t=1;i<=n;i++)//映射坐标为点
for(int j=1;j<=m;j++,t++)
ids[i][j]=t;
int x1,x2,y1,y2;
while(cin>>x1>>y1>>x2>>y2)
{
int a=ids[x1][y1],b=ids[x2][y2];
p[find(a)]=find(b);//假如是连通块了,则加到同个连通块中
}
edge();//处理每个走的方式
int res=0;
for(int i=0;i<k;i++)
{
int a=find(e[i].a),b=find(e[i].b),w=e[i].w;
if(a!=b)//假如不在一个连通块
{
res+=w;//则加上边权
p[a]=b;//加到同一个连通块中
}
}
cout<<res<<endl;
return 0;
}