最小生成树:对于一个无向连通图的最小生成树,选取边使得图中每个顶点连通且花费最小。
在kruskal算法中,集合A是一个森林,加入集合A中的安全边总是图中连接两个不同连通分支的最小权边。prim算法中,集合A仅形成单颗树,添加入集合A的安全边总是连接树与一个不在树中的顶点的最小权边。
kruskal在图G(v,e)上的运行时间取决于不相交集合数据结构是如何实现的,模板中采用路径优化的并查集,时间复杂度为O(1)。然后对时间复杂度有影响的就是对边的排序,其最终时间复杂度O(E lgV);
prim算法适用于边多的,反之为kruskal算法。鉴于kruskal代码的简单易操作,例题解法均为kruskal算法。
Kruskal伪代码:
(1)对所有的边从小到大排序
(2)while(n>1) do{
取权值最小的边(u,v);
if(u,v不连通){
将(u,v)加入T;
n--;
}
将边(u,v)从集合E中删除;
}
其中判断两点是否连通可使用并查集
/*zhizhao zhuo
Kruskal 基本模板*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3;
struct Edge{
int from,to,dist;
Edge(int f,int t,int d):from(f),to(t),dist(d){}
bool operator <(const Edge& a){
return dist<a.dist;
}
};
vector<Edge>edges;
int pre[maxn];
int find(int x){
int i=x;
while(pre[i]!=i)i=pre[i];
int j=x,k;
while(j!=pre[j]){
k=pre[j];
pre[j]=i;
j=k;
}
return i;
}
void joint(int x,int y){
if(find(x)!=find(y))pre[find(x)]=find(y);
}
int kruskal(){
int sum=0;
sort(edges.begin(),edges.end());
for(int i=0;i<edges.size();i++){
int x=find(edges[i].from),y=find(edges[i].to);
if(x!=y){
sum+=edges[i].dist;
pre[x]=y;
}
}
return sum;
}
int main(){
int v,e;
while(~scanf("%d%d",&v,&e)){
for(int i=0;i<=v;i++)pre[i]=i;
edges.clear();
for(int i=0;i<e;i++){
int f,t,d;
scanf("%d%d%d",&f,&t,&d);
edges.push_back(Edge(f,t,d));
}
int ans=kruskal();
printf("%d\n",ans);
}
return 0;
}
基础例题:HDU-3371 Connect the cities(会给出一些已经连接好的城市,提前加入并查集即可。遍历pre数组即可知道是否已经构成了生成树)
/*zhizhao zhuoHDU-3371 Connect the cities*/
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=500+10;
struct Edge{
int from,to,dist;
Edge(int f,int t,int d):from(f),to(t),dist(d){}
bool operator <(const Edge& a){
return dist<a.dist;
}
};
vector<Edge>edges;
int pre[maxn],T[maxn];
int find(int x){
int i=x;
while(pre[i]!=i)i=pre[i];
int j=x,k;
while(j!=pre[j]){
k=pre[j];
pre[j]=i;
j=k;
}
return i;
}
void joint(int x,int y){if(find(x)!=find(y))pre[find(x)]=find(y);}
int kruskal(){
int sum=0;
sort(edges.begin(),edges.end());
for(int i=0;i<edges.size();i++){
int x=find(edges[i].from),y=find(edges[i].to);
if(x!=y){
sum+=edges[i].dist;
pre[x]=y;
}
}
return sum;
}
int main(){
int n,m,k,Case;
scanf("%d",&Case);
while(Case--){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)pre[i]=i;
edges.clear();
for(int i=0;i<m;i++){
int f,t,d;
scanf("%d%d%d",&f,&t,&d);
edges.push_back(Edge(f,t,d));
}
for(int i=0;i<k;i++){
int t;
scanf("%d",&t);
for(int j=0;j<t;j++)scanf("%d",&T[j]);
for(int j=1;j<t;j++)joint(T[0],T[j]);
}
int ans=kruskal();
bool mark=true;
for(int i=2;i<=n;i++)if(find(1)!=find(i)){mark=false;break;}
if(mark==true)printf("%d\n",ans);
else printf("-1\n");
}
return 0;
}
理解不相交集合数据结构(并查集)对最小生成树的作用:UVA 1395 Slim Span
(构建一个生成树,使得生成树的最长边减去最短边所得的值最小。对于n个顶点的图,构成生成树至少需要n-1条边。
每次枚举一个连续的边集区间(L,R),比较每次的结果求最小值。注意提前排好序并且每次都需要将pre【】数组初始化)
/*zhizhao zhuo UVA 1395 Slim Span*/
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
#define LL long long
const int maxn=1e2+10,INF=1e8;
struct Edge{
int from,to,dist;
Edge(int f,int t,int d):from(f),to(t),dist(d){}
bool operator <(const Edge& a){
return dist<a.dist;
}
};
vector<Edge>edges;
int pre[maxn];
int find(int x){
int i=x;
while(pre[i]!=i)i=pre[i];
int j=x,k;
while(j!=pre[j]){
k=pre[j];
pre[j]=i;
j=k;
}
return i;
}
void joint(int x,int y){
if(find(x)!=find(y))pre[find(x)]=find(y);
}
int Kruskal(int k,int n){
int sum=0;
int Min=INF,Max=-INF;
for(int i=k;i<edges.size();i++){
int x=find(edges[i].from),y=find(edges[i].to);
if(x!=y){
pre[x]=y;
sum++;
Min=min(Min,edges[i].dist);
Max=max(Max,edges[i].dist);
}
if(sum==n-1)break;
}
if(sum==n-1)return Max-Min;
return INF;
}
int main(){
int n,m;
while(scanf("%d%d",&n,&m)==2 &&n){
int ans=INF;
edges.clear();
for(int i=0;i<m;i++){
int f,t,d;
scanf("%d%d%d",&f,&t,&d);
edges.push_back(Edge(f,t,d));
}
sort(edges.begin(),edges.end());
for(int i=0;i<edges.size();i++){
for(int j=0;j<=n;j++)pre[j]=j;
if(edges.size()-i>=n-1)ans=min(ans,Kruskal(i,n)); //至少要n-1条边才能构成生成树
}
if(ans==INF)printf("-1\n");
else printf("%d\n",ans);
}
return 0;
}
HDU-4313 Matrix(给定一些点和网络,删除网络上的边使得这些点都不连通,且花费最小。思路:不在构建最小生成树,而是构建最大生成树(边按从大到小排序),当边的两点都是给定的点时,花费加上这条边,反之加入最大生成树。最后得到的是多个包含一个给定点的最大生成树,最大生成树之间不相连,自己思考一下为什么达到了题目的条件)
/*zhizhao zhuo HDU-4313 Matrix*/
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define LL long long
const int maxn=1e5+10;
struct Edge{
int from,to,dist;
Edge(int f,int t,int d):from(f),to(t),dist(d){}
bool operator <(const Edge& a){
return dist>a.dist;
}
};
vector<Edge>edges;
int pre[maxn],v[maxn];
int find(int x){
int i=x;
while(pre[i]!=i)i=pre[i];
int j=x,k;
while(j!=pre[j]){
k=pre[j];
pre[j]=i;
j=k;
}
return i;
}
/*
void joint(int x,int y){
if(find(x)!=find(y))pre[find(x)]=find(y);
}*/
LL kruskal(int k){
LL sum=0;
sort(edges.begin(),edges.end());
for(int i=0;i<edges.size();i++){
int x=find(edges[i].from),y=find(edges[i].to);
if(v[x]&&v[y]){
sum+=edges[i].dist;continue;
}
else if(v[x])pre[y]=x;
else pre[x]=y;
}
return sum;
}
int main(){
int T,N,K;
scanf("%d",&T);
while(T--){
scanf("%d%d",&N,&K);
memset(v,0,sizeof(v));
for(int i=0;i<=N;i++)pre[i]=i;
edges.clear();
for(int i=0;i<N-1;i++){
int f,t,d;
scanf("%d%d%d",&f,&t,&d);
edges.push_back(Edge(f,t,d));
}
for(int i=0;i<K;i++){
int t;
scanf("%d",&t);
v[t]=1;
}
LL ans=kruskal(K);
printf("%lld\n",ans);
}
return 0;
}
理解生成树由n-1条边组成,学会子集生成:UVA 1151 Buy or Build(给一张图和几个子网,子网已经连通且花费固定,可以直接连通两点或者采用子网,使得把图连通的花费最小)
运用子集生成枚举子网,将子网中的点加入并查集,再构建生成树
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1000 +10;
const int maxq=9;
int xx[maxn],yy[maxn],cost[maxq];
int pre[maxn]; //并查集数组
vector<int >subn[maxq]; //子网载体
struct Edge{
int u,v,d;
Edge(int u,int v,int d):u(u),v(v),d(d){} //方便后序的插入操作
bool operator <(const Edge &a) //用于排序
const {return d<a.d;}
};
int find(int lin){ //并查集查找函数(路径优化)
int i=lin;
while(pre[i]!=i)i=pre[i];
int j=lin,k;
while(j!=pre[j]){
k=pre[j];
pre[j]=i;
j=k;
}
return i;
}
int MST(int cnt,const vector<Edge> & e,vector<Edge>& used){ //最小生成树 重点为cnt的值
if(cnt==1)return 0;
int ans=0;
int m=e.size();
used.clear();
for(int i=0;i<m;i++){
int x=find(e[i].u),y=find(e[i].v);
int d=e[i].d;
if(x!=y){
pre[x]=y;
ans+=d;
used.push_back(e[i]); //装入生成MST所需要用到的边,用于后续的子集生成模块
if(--cnt==1)break;
}
}
return ans;
}
int main(){
int T,n,q;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&q);
for(int i=0;i<q;i++){
int sum,c,temp;
scanf("%d%d",&sum,&c);
cost[i]=c;
subn[i].clear();
while(sum--){
scanf("%d",&temp);
subn[i].push_back(temp-1);
}
}
for(int i=0;i<n;i++)scanf("%d%d",&xx[i],&yy[i]);
vector<Edge> e, need;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
int c=(xx[i]-xx[j])*(xx[i]-xx[j])+(yy[i]-yy[j])*(yy[i]-yy[j]);
e.push_back(Edge(i,j,c));
}
}
for(int i=0;i<n;i++)pre[i]=i;
sort(e.begin(),e.end());
int ans=MST(n,e,need);
vector<Edge>sun;
for(int mask=0;mask< (1<<q);mask++){ //子集生成模块
int cnt=n,c=0;
for(int i=0;i<n;i++)pre[i]=i; //子网中加入的点与后序的MST都是这一个集合,子网已连接的在后续MST中不会再查找
for(int i=0;i<q;i++)if(mask& (1<<i)){
c+=cost[i];
int ss=subn[i].size();
for(int j=0;j<ss;j++){
int u=find(subn[i][j]),v=find(subn[i][0]); //将子网中的点加入集合。
if(u!=v){pre[u]=v;--cnt;}
}
}
ans=min(ans, c+ MST(cnt,need,sun)); // 二次MST
}
printf("%d\n",ans);
if(T)printf("\n");
}
return 0;
}
持续更新中。。。。。。