51nod1212最小生成树模板题:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1212
克鲁斯卡尔(加边法):先取最小的边,在判断是不是一个集合(不是舍去,是加上)
普里姆(加点法):先已经判断了不是一个集合,再从不是的集合中找出最小的边把点加入,最后更新(再取,再更新。。)
都是加到n-1条边停止(n个点最少由n-1条边连通),另外,Kruskal不用考虑重边(并查集自动取舍),Prim需要考虑重边(不考虑必错!)
(关于去最小边,克鲁斯卡尔事先排序好按顺序取即可,所以是n*logn;普里姆每轮之后都要更新,所以每次取都循环找一次,所以是n*n,当然最后还可以优化这里就不说了)
克鲁斯卡尔
以边为重点进行各种操作,点不是重点,所以存图方式不重要很简单。(把边连接信息表示出来就行,结构体完美表示)
另外关于并查集,初学并查集推荐不用深度优化,只改变父节点就行更加好理解,而且时间也差不了多少
初级并查集
1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 #include <iomanip> 5 #include <cstdio> 6 #include <cstring> 7 using namespace std; 8 const int maxn=1e6+5; 9 const int inf=1e9; 10 int fa[maxn],ra[maxn];//初学并查集推荐不用深度优化,只改变父节点就行好理解,而且时间也差不了多少 11 int n,m;//点数,边数 12 int ans,cnt;//记录边数 13 struct px 14 { 15 int from; 16 int to; 17 int w; 18 }T[maxn]; 19 bool cmp(px aa,px bb) 20 { 21 return aa.w<bb.w; 22 } 23 24 void Init() 25 { 26 for(int i=1;i<=n;i++) fa[i]=i;//祖先初始化,深度初始1 27 } 28 29 int FindFa(int x) 30 { 31 return x==fa[x]?x:fa[x]=FindFa(fa[x]);//优化1,路经压缩 32 } 33 34 void Kruskal() 35 { 36 for(int i=1;i<=m;i++) 37 { 38 int fx=FindFa(T[i].from),fy=FindFa(T[i].to); 39 if(fx!=fy) 40 { 41 fa[fy]=fx;//个人习惯从后往前接,只改变父结点不管深度 42 43 ans+=T[i].w; 44 cnt++; 45 if(cnt==n-1) break;//n-1边够连通了,退出 46 } 47 } 48 49 } 50 51 int main() 52 { 53 ios::sync_with_stdio(false); cin.tie(0); 54 55 cin>>n>>m; 56 for(int i=1;i<=m;i++) cin>>T[i].from>>T[i].to>>T[i].w; 57 58 sort(T+1,T+1+m,cmp); 59 Init(); 60 Kruskal(); 61 62 if(cnt<n-1) cout<<"-1"<<endl; 63 else cout<<ans<<endl; 64 65 return 0; 66 }
高级并查集
1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 #include <iomanip> 5 #include <cstdio> 6 #include <cstring> 7 using namespace std; 8 const int maxn=1e6+5; 9 const int inf=1e9; 10 int fa[maxn],ra[maxn]; 11 int n,m;//点数,边数 12 int ans,cnt;//记录边数 13 struct px 14 { 15 int from; 16 int to; 17 int w; 18 }T[maxn]; 19 bool cmp(px aa,px bb) 20 { 21 return aa.w<bb.w; 22 } 23 24 void Init() 25 { 26 for(int i=1;i<=n;i++) { fa[i]=i; ra[i]=1; }//祖先初始化,深度初始1 27 } 28 29 int FindFa(int x) 30 { 31 return x==fa[x]?x:fa[x]=FindFa(fa[x]);//优化1,路经压缩 32 } 33 34 void Kruskal() 35 { 36 for(int i=1;i<=m;i++) 37 { 38 int fx=FindFa(T[i].from),fy=FindFa(T[i].to); 39 if(fx!=fy) 40 { 41 if(ra[fx]>=ra[fy]) { ra[fx]+=ra[fy]; fa[fy]=fx; }//优化2,小树往大树上合并,减少以后判断修改次数 42 else { ra[fy]+=ra[fx]; fa[fx]=fy; } 43 44 ans+=T[i].w; 45 cnt++; 46 if(cnt==n-1) break;//n-1边够连通了,退出 47 } 48 } 49 50 } 51 52 int main() 53 { 54 ios::sync_with_stdio(false); cin.tie(0); 55 56 cin>>n>>m; 57 for(int i=1;i<=m;i++) cin>>T[i].from>>T[i].to>>T[i].w; 58 59 sort(T+1,T+1+m,cmp); 60 Init(); 61 Kruskal(); 62 63 if(cnt<n-1) cout<<"-1"<<endl; 64 else cout<<ans<<endl; 65 66 return 0; 67 }
普里姆
以点为重点进行各种操作,所以存图方式就非常重要,要尽可能把图上信息还原回来。
这里介绍两种存图方式:邻接矩阵(简单图可以,复杂图容易爆范围),链式前向星(链式存,不怕爆)。。(邻接表就不说了不直观,前向星可替代)
初级版:邻接数组
1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 #include <iomanip> 5 #include <cstdio> 6 #include <cstring> 7 using namespace std; 8 const int maxn=1e6+5; 9 const int inf=1e9; 10 int dist[maxn],vis[maxn]; 11 int Map[1005][1005]; 12 int n,m;//顶点数,边数 13 int ans;//记录和 14 15 void Init() 16 { 17 for(int i=1;i<=n;i++)//初始化边权值 18 { 19 for(int j=1;j<=n;j++) 20 { 21 if(i==j) Map[i][j]=0; 22 else Map[i][j]=inf; 23 } 24 } 25 memset(dist,0,sizeof(dist));//初始化最小权值 26 memset(vis,0,sizeof(vis));//初始化访问记录 27 } 28 29 void Prim(int x) 30 { 31 int temp,lowcast;//记录最小权值顶点和最小权值 32 33 for(int i=1;i<=n;i++) dist[i]=Map[x][i];//与该集合内所有连接点的最小权值 34 vis[x]=1; 35 for(int i=1;i<=n-1;i++)//n-1条边即可退出 36 { 37 lowcast=inf; 38 for(int j=1;j<=n;j++)//循环一论,找出所有连接点中的最小权值 39 { 40 if(!vis[j] && dist[j]<lowcast) 41 { 42 lowcast=dist[j]; 43 temp=j; 44 } 45 } 46 47 vis[temp]=1;//更新集合,加入点 48 ans+=lowcast; 49 if(ans>=inf) break;//说明没有点连接,不连通,退出 50 51 for(int j=1;j<=n;j++) if(!vis[j] && dist[j]>Map[temp][j]) dist[j]=Map[temp][j];//根据新加入的点更新所有连接点的最小权值 52 } 53 } 54 55 int main() 56 { 57 ios::sync_with_stdio(false); cin.tie(0); 58 59 cin>>n>>m; 60 Init();//注意初始化无限大必须放在输入之前! 61 for(int i=1;i<=m;i++) 62 { 63 int x,y,z; 64 cin>>x>>y>>z; 65 66 if(z<Map[x][y])//考虑重边情况!(不考虑重边必错) 67 { 68 Map[x][y]=z; 69 Map[y][x]=z; 70 } 71 } 72 73 Prim(1); 74 75 if(ans>=inf) cout<<"-1"<<endl; 76 else cout<<ans<<endl; 77 78 return 0; 79 }
中级版:链式前向星
1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 #include <iomanip> 5 #include <cstdio> 6 #include <cstring> 7 using namespace std; 8 const int maxn=1e4+5;//最大点数 9 const int maxm=1e5*2;//最大边数 10 const int inf=1e9; 11 int dist[maxn],vis[maxn],head[maxn]; 12 //int Map[maxn][maxn]; 13 int n,m;//顶点数,边数 14 int ans,cnt;//记录和,所有边数量 15 struct px 16 { 17 int next; 18 int to; 19 int w; 20 }T[maxm*3];//注意T存的是所有边数(边的连接信息,终点,权值,下条边都在这里),无向图是2倍 21 22 23 void Init() 24 { 25 for(int i=1;i<=n;i++) dist[i]=inf;//初始化最小权值 26 memset(vis,0,sizeof(vis));//初始化访问记录 27 } 28 29 void Add(int x,int y,int z) 30 { 31 T[++cnt].next=head[x]; 32 T[cnt].to=y; 33 T[cnt].w=z; 34 head[x]=cnt; 35 } 36 37 void Prim(int x) 38 { 39 int temp,lowcast;//记录最小权值顶点和最小权值 40 41 //本意就是找与该集合内所有连接点的最小权值,两种方式都可以,第2种更好(链式存储) 42 //for(int i=1;i<=n;i++) dist[i]=Map[x][i];//不管连接与否,找到一遍(没连接的为inf) 43 for(int i=head[x];i;i=T[i].next)//只找连接的,没连接的直接不找(dist事先初始化inf效果一样) 44 { 45 if(T[i].w<dist[T[i].to])//注意重边情况! 46 { 47 dist[T[i].to]=T[i].w; 48 } 49 } 50 51 vis[x]=1; 52 for(int i=1;i<=n-1;i++)//n-1条边即可退出 53 { 54 lowcast=inf; 55 for(int j=1;j<=n;j++)//循环一论,找出所有连接点中的最小权值 56 { 57 if(!vis[j] && dist[j]<lowcast) 58 { 59 lowcast=dist[j]; 60 temp=j; 61 } 62 } 63 64 vis[temp]=1;//更新集合,加入点 65 ans+=lowcast; 66 if(ans>=inf) break;//说明没有点连接,不连通,退出 67 68 //for(int j=1;j<=n;j++) if(!vis[j] && dist[j]>Map[temp][j]) dist[j]=Map[temp][j];//根据新加入的点更新所有连接点的最小权值 69 for(int j=head[temp];j;j=T[j].next) 70 { 71 int J=T[j].to;//注意此J非彼j(J才是连接点终点(没有到达的点),j存放连接点的结构体序号位置) 72 if(!vis[J] && dist[J]>T[j].w) 73 { 74 dist[J]=T[j].w; 75 } 76 } 77 } 78 } 79 80 int main() 81 { 82 ios::sync_with_stdio(false); cin.tie(0); 83 84 cin>>n>>m; 85 Init();//注意初始化无限大必须放在输入之前! 86 for(int i=1;i<=m;i++) 87 { 88 int x,y,z; 89 cin>>x>>y>>z; 90 91 Add(x,y,z); 92 Add(y,x,z); 93 } 94 95 Prim(1); 96 97 if(ans>=inf) cout<<"-1"<<endl; 98 else cout<<ans<<endl; 99 100 return 0; 101 }
高级版:链式前向星+堆优化(其实不管是否优化,最开始的源点操作放在循环里与循坏外都是可以行得通!只不过优化前放到外面是初学为了逻辑更清晰先把源点放进去再操作n-1次;优化后放到循环内是为了方便统一理解操作,此时优化已经建立在逻辑清晰的基础上了!)
1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 #include <iomanip> 5 #include <queue> 6 #include <cstdio> 7 #include <cstring> 8 using namespace std; 9 const int maxn=1e4+5;//最大点数 10 const int maxm=1e5*2;//最大边数 11 const int inf=1e9; 12 int dist[maxn],vis[maxn],head[maxn]; 13 //int Map[maxn][maxn]; 14 int n,m;//顶点数,边数 15 int ans,cnt,Cnt;//记录和,所有边数量 16 //和没优化时不同,不能用inf判断是否能生成了(因为0的情况可能出现此时又没加inf),所以需要用Cnt记录加入了几个点 17 struct px 18 { 19 int next; 20 int to; 21 int w; 22 }T[maxm*3];//注意T存的是所有边数(边的连接信息,终点,权值,下条边都在这里),无向图是2倍 23 struct pxx 24 { 25 int j; 26 int dist; 27 bool operator<(const pxx &a) const 28 { 29 return dist>a.dist; 30 } 31 }Tmp; 32 priority_queue<pxx> que; 33 34 void Init() 35 { 36 for(int i=1;i<=n;i++) dist[i]=inf;//初始化最小权值 37 memset(vis,0,sizeof(vis));//初始化访问记录 38 } 39 40 void Add(int x,int y,int z) 41 { 42 T[++cnt].next=head[x]; 43 T[cnt].to=y; 44 T[cnt].w=z; 45 head[x]=cnt; 46 } 47 48 void Prim(int x) 49 { 50 dist[x]=0;//不能少,且vis[x]=1不能要(因为要在循环里操作) 51 Tmp.j=x; 52 Tmp.dist=0; 53 que.push(Tmp); 54 55 while(!que.empty())//循环从s开始操作(区别没优化时:s在循环外操作,从s的邻接点开始循环操作) 56 { 57 pxx tmp=que.top(); 58 que.pop(); 59 if(vis[tmp.j]) continue; 60 61 vis[tmp.j]=1; 62 ans+=tmp.dist;//Prim会用到 63 Cnt++;//和没优化时区别,记录点数 64 for(int j=head[tmp.j];j;j=T[j].next) 65 { 66 int J=T[j].to;//注意此J非彼j(J才是连接点终点(没有到达的点),j存放连接点的结构体序号位置) 67 if(dist[J]>T[j].w) 68 { 69 dist[J]=T[j].w; 70 Tmp.j=J; 71 Tmp.dist=dist[J]; 72 que.push(Tmp); 73 } 74 } 75 } 76 } 77 78 int main() 79 { 80 ios::sync_with_stdio(false); cin.tie(0); 81 82 cin>>n>>m; 83 Init();//注意初始化无限大必须放在输入之前! 84 for(int i=1;i<=m;i++) 85 { 86 int x,y,z; 87 cin>>x>>y>>z; 88 89 Add(x,y,z); 90 Add(y,x,z); 91 } 92 93 Prim(1); 94 95 if(Cnt<n) cout<<"orz"<<endl; 96 else cout<<ans<<endl; 97 98 return 0; 99 }
链式前向星改为vector
vector+堆优化
1 #include <iostream> 2 #include <string> 3 #include <algorithm> 4 #include <iomanip> 5 #include <vector> 6 #include <queue> 7 #include <cstdio> 8 #include <cstring> 9 using namespace std; 10 const int maxn=1e4+5;//最大点数 11 const int maxm=1e6+5;//最大边数 12 const int inf=1e9; 13 int dist[maxn],vis[maxn]; 14 //int Map[maxn][maxn]; 15 int n,m;//顶点数,边数 16 int ans,cnt,Cnt;//记录和,所有边数量 17 //和没优化时不同,不能用inf判断是否能生成了(因为0的情况可能出现此时又没加inf),所以需要用Cnt记录加入了几个点 18 struct px 19 { 20 int to; 21 int w; 22 }T; 23 vector<px> vec[maxn]; 24 struct pxx 25 { 26 int j; 27 int dist; 28 bool operator<(const pxx &a) const 29 { 30 return dist>a.dist; 31 } 32 }Tmp; 33 priority_queue<pxx> que; 34 35 void Init() 36 { 37 for(int i=1;i<=n;i++) dist[i]=inf;//初始化最小权值 38 memset(vis,0,sizeof(vis));//初始化访问记录 39 } 40 41 void Prim(int x) 42 { 43 dist[x]=0;//不能少,且vis[x]=1不能要(因为要在循环里操作) 44 Tmp.j=x; 45 Tmp.dist=0; 46 que.push(Tmp); 47 48 while(!que.empty())//循环从s开始操作(区别没优化时:s在循环外操作,从s的邻接点开始循环操作) 49 { 50 pxx tmp=que.top();//重边时也考虑到了,自动取得最小值 51 que.pop(); 52 if(vis[tmp.j]) continue;//重点重边时也考虑到了,返回 53 54 vis[tmp.j]=1; 55 ans+=tmp.dist;//Prim会用到 56 Cnt++;//和没优化时区别,记录点数 57 for(int j=0;j<vec[tmp.j].size();j++) 58 { 59 int J=vec[tmp.j][j].to,W=vec[tmp.j][j].w; 60 if(dist[J]>W) 61 { 62 dist[J]=W; 63 Tmp.j=J; 64 Tmp.dist=dist[J]; 65 que.push(Tmp); 66 } 67 } 68 } 69 } 70 71 int main() 72 { 73 ios::sync_with_stdio(false); cin.tie(0); 74 75 cin>>n>>m; 76 Init();//注意初始化无限大必须放在输入之前! 77 for(int i=1;i<=m;i++) 78 { 79 int x,y,z; 80 cin>>x>>y>>z; 81 82 T.to=y; 83 T.w=z; 84 vec[x].push_back(T); 85 T.to=x; 86 vec[y].push_back(T);//双向图 87 } 88 89 Prim(1); 90 91 if(Cnt<n) cout<<"orz"<<endl; 92 else cout<<ans<<endl; 93 94 return 0; 95 }
一般情况下先vector好理解,一般够用了,不行了再换链式前向星!
完。
来源:https://www.cnblogs.com/redblackk/p/9712587.html