六、最小生成树
生成树:由一个图的所有点与部分边构成的连通又无回路的树
最小生成树:权值最小的生成树
1.Prim算法
(1)思想:
设置点集T,边集U,一开始T,U为空,首先任意选取一点V0加入T,更新所有以V0为端点的边的另一端点Vi到T中点的最小距离dis[i],然后进行以下操作:
①选取不在T中的点vj,vj到T的距离是可选点中最小的,将vj加入T
②将连接vj的边,而且另一端点不在T中的边加入U
③更新所有加入边的另一端点vk的dis[k],dis[k]=min{dis[k],val(j,k)}
重复这三步,直到|T|=n
注意:②中所加入的边的另一端点一定不再T中,否则就会成环
(2)分析:
遍历取最小边o(v^2),堆取最小边o(Elog(v))
不加堆优化的Prim适用于密集图
加堆优化的Prim适用于稀疏图
(3)模板:
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define INF 1<<30;
int n;
struct edge{
int to,cost;
edge(int to,int cost):to(to),cost(cost){}
bool operator < (const edge& e)const{
return cost>e.cost;
}
};
vector<edge> G[110];
int prim(void){
priority_queue<edge> q;
int num=0;
int ans=0;
int dis[110];
for(int i=1;i<=n;i++){
dis[i]=INF;
}
int vis[110];
memset(vis,0,sizeof(vis));
edge e(1,0);
edge te(0,0);
q.push(e);
while(num<n&&!q.empty()){
do{
te=q.top();q.pop();
//cout<<te.to<<' '<<te.cost<<endl;
}while(vis[te.to]==1&&!q.empty());
if(vis[te.to]==0){
num++;ans+=te.cost;vis[te.to]=1;
for(int i=0;i<G[te.to].size();i++){
int k=G[te.to][i].to,c=G[te.to][i].cost;
if(vis[k]==0){
if(dis[k]>c){
dis[k]=c;
q.push(edge(k,c));
}
}
}
}
}
if(num<n)return -1;
return ans;
}
int main(){
while(cin>>n){
for(int i=1;i<=n;i++){
G[i].clear();
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int c;cin>>c;
edge e(j,c);
G[i].push_back(e);
}
}
cout<<prim()<<endl;
}
return 0;
}
2.Kruskal算法
(1)思想:
将所有边从小到大排序,选取最短的边,且边的两个端点分属不同的集合,一直加满n-1条边为止
(2)分析:
边排序最耗时,为O(ElogE)
(3)模板代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#define MEM(a,x) memset(a,x,sizeof(a))
#define ll long long
#define INF 0x3f3f3f3f
#define EXP 1e-10
using namespace std;
int n;
struct edge{
int s,e,v;
edge(int s,int e,int v):s(s),e(e),v(v){}
bool operator < (const edge& e)const{
return v<e.v;
}
};
vector<edge> G;
int fa[110];
int find(int i){
if(fa[i]==i)return i;
return fa[i]=find(fa[i]);
}
void unite(int i,int j){
int fi=find(i);
int fj=find(j);
if(fi==fj)return;
fa[fi]=fj;
}
int main(){
while(cin>>n){
G.clear();
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int val;
cin>>val;
G.push_back(edge(i,j,val));
}
}
sort(G.begin(),G.end());
int ans=0,num=0;
for(int i=1;num<n-1&&i<G.size();i++){
int ss=G[i].s,ee=G[i].e,vv=G[i].v;
if(find(ss)!=find(ee)){
ans+=vv;
num++;
unite(ss,ee);
}
}
if(num<n-1)cout<<"-1"<<endl;
else cout<<ans<<endl;
}
return 0;
}
3.补充应用
分析:n个村庄,k个卫星,问剩余村庄最小的最大连接距离d
不考虑卫星的情况下,d要尽可能小,如果我们求出了最小生成树,那么这个生成树上最长的边就是要求的d;当我们考虑卫星的时候,实际上就是说可以有多棵子树,子树之间用卫星联系,求子树的d即可;实际操作就是将前k-1大的边删去,第k大的边就是答案
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<iomanip>
#define MEM(a,x) memset(a,x,sizeof(a))
#define ll long long
#define INF 0x3f3f3f3f
#define EXP 1e-10
using namespace std;
int k,n;
double x[510],y[510];
struct edge{
int s,e;
double v;
edge(int s,int e,double v):s(s),e(e),v(v){}
bool operator < (const edge& e)const{
return v<e.v;
}
};
vector<edge> G;
vector<double> EDGE;
int fa[510];
int find(int i){
if(fa[i]==i)return i;
return fa[i]=find(fa[i]);
}
void unite(int i,int j){
int fi=find(i);
int fj=find(j);
if(fi==fj)return;
fa[fi]=fj;
}
int main(){
int m;
cin>>m;
while(m--){
G.clear();
EDGE.clear();
cin>>k>>n;
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<=n;i++){
cin>>x[i]>>y[i];
}
//cout<<x[3]<<' '<<y[3]<<endl;
//cout<<x[4]<<' '<<y[4]<<endl;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
double val;
if(i==j)val=0;
else{
val=sqrt(abs((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])));
}
G.push_back(edge(i,j,val));
}
}
sort(G.begin(),G.end());
int num=0;
for(int i=0;num<n-1&&i<G.size();i++){
int ss=G[i].s,ee=G[i].e;
double vv=G[i].v;
if(find(ss)!=find(ee)){
EDGE.push_back(vv);
unite(ss,ee);
num++;
}
}
sort(EDGE.begin(),EDGE.end());
cout<<fixed<<setprecision(2)<<EDGE[n-k-1]<<endl;
}
return 0;
}