基礎圖論算法,今年(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 學習,我發現如下規律:
沒了