六、最小生成树
生成树:由一个图的所有点与部分边构成的连通又无回路的树
最小生成树:权值最小的生成树
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; }