基础图论算法,今年(2019csp-JX)提升就考了,因此很重要。ios
在一个有n个节点的连通图中找到n-1条边,使得其连同全部点,且边权和最短。算法
(以上是本人空口BB,不要相信他)数组
给定一张边带权的无向图,G=(V,E),n=|V|,m=|E|。由 V 中所有 n 个顶点和 E 中 n-1 条边构成的无向连通子图网络
被称为 G 的一棵生成树。边的权值之和最小的生成树被称为无向图 G 的最小生成树(Minimum Spanning Tree,MST)。学习
任意一棵最小生成树必定包含无向图中权值最小的边(自证不难)优化
Kruskal算法就是基于以上定理的,它的流程以下:spa
(是否是很是简单易懂)易得,时间复杂度 O(m log m).code
代码以下:排序
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<string> using namespace std; int n,m,i,j,u,v,total; struct edge{ int start,to;long long val; }bian[2000005]; int f[100000]; long long ans; int find(int x)//并查集部分 { if (f[x]==x) return x; return f[x]=find(f[x]); } bool cmp(edge a,edge b)//结构体快排时用到的 { return a.val<b.val; } inline void kruskal()//最小生成树 { for(int i=1;i<=m;i++) { u=find(bian[i].start); v=find(bian[i].to); if(u==v) continue;//判断在不在同一个并查集里面,在就下一个循环 ans+=bian[i].val;//不在,就加上 f[u]=v;//链接两个并查集 total++; if(total==n-1) break;//当造成了最小生成树后,退出(以后作的也没用了) } } int main() { scanf("%d%d",&n,&m); for(i=1;i<=n;i++) f[i]=i; for(i=1;i<=m;i++) { scanf("%d%d%d",&bian[i].start,&bian[i].to,&bian[i].val); } sort(bian+1,bian+m+1,cmp);//快排边长 kruskal(); printf("%d",ans); return 0; }
(这代码很丑,由于不是我本身写的,之前本身写的找不到了,懒得再写一遍,见谅)string
(不得不说,它长得很像 Dijkstra 的代码)
代码思路以下:
易得,时间复杂度: \(O(n^2)\) ,可用小根堆优化为 O(m log n)。
但这样不如 Kruskal 算法方便,但当m远远>n时(特别是稠密图),它更优。
代码长这个样子:
#include <cstdio> #include <cstring> #include <cctype> #include <stdlib.h> #include <string> #include <map> #include <iostream> #include <set> #include <stack> #include <cmath> #include <queue> #include <vector> #include <algorithm> using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) typedef long long ll; typedef pair<int,int> pir; const int N=5000+10; const int M=200000+10; int first[N],tot; int vis[N],dis[N],n,m; priority_queue <pir,vector<pir>,greater<pir> >q; struct edge{ int v,w,next; } e[M*2]; void add_edge(int u,int v,int w) { e[tot].v=v; e[tot].w=w; e[tot].next=first[u]; first[u]=tot++; } void init() { mem(first,-1); tot=0; mem(dis,127); } void prim() { int cnt=0,sum=0; dis[1]=0; q.push(make_pair(0,1)); while(!q.empty()&&cnt<n) { int d=q.top().first,u=q.top().second; q.pop(); if(!vis[u]) { cnt++; sum+=d; vis[u]=1; for(int i=first[u]; ~i; i=e[i].next) if(e[i].w<dis[e[i].v]) { dis[e[i].v]=e[i].w; q.push(make_pair(dis[e[i].v],e[i].v)); } } } if(cnt==n) printf("%d\n",sum); else puts("orz"); } int main() { int u,v,w; init(); scanf("%d%d",&n,&m); for(int i=1; i<=m; i++) { scanf("%d%d%d",&u,&v,&w); add_edge(u,v,w); add_edge(v,u,w); } prim(); return 0; }
若是你不想写优化代码,请看这里:
#include<cstdio> #include<algorithm> #include<cstring> #define maxn 5050 #define maxd 999999 using namespace std; int n,m,x,y,z,f[maxn][maxn],ans[maxn]; bool use[maxn]; void prim(){ memset(ans,maxd,sizeof(ans)); memset(use,false,sizeof(use)); ans[1]=0; for(int i=1;i<n;i++){ int best=maxd,k=0; for(int j=1;j<=n;j++){ if(!use[j] && ans[j]<best){best=ans[j];k=j;} } use[k]=true; for(int j=1;j<=n;j++){ if(!use[j]){ ans[j]=min(ans[j],f[j][k]); } } } int main(){ scanf("%d %d",&n,&m); memset(f,maxd,sizeof(f)); for(int i=1;i<=m;i++){ scanf("%d %d %d",&x,&y,&z); f[x][y]=f[y][x]=min(f[x][y],z); } prim(); int sum=0; for(int i=1;i<=n;i++){ sum+=ans[i]; //printf("%d\n",ans[i]); } printf("%d\n",sum); return 0; }
而后就没了。
如今假设有一个很实际的问题:咱们要在n个城市中创建一个通讯网络,则连通这n个城市须要布置n-1一条通讯线路,这个时候咱们须要考虑如何在成本最低的状况下创建这个通讯网?
发现这就是最小生成树裸题。
通过(多年)1 年的 OI 学习,我发现如下规律:
没了