最小生成樹

最小生成樹

基礎圖論算法,今年(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

Kruskal算法就是基於以上定理的,它的流程以下:spa

  1. 創建並查集,每一個點各自構成一個集合。
  2. 把全部便按照權值從小到大排序,一次掃描每條邊(x,y,z)。
  3. 若 x,y 均屬於同一集合,就忽略這條邊,繼續掃描下一條。
  4. 不然,合併 x,y 所在集合,ans+=z。
  5. 全部邊掃描完成事後結束算法。(固然在答案肯定有解的狀況下當所連邊數≥n-1時亦可退出)

是否是很是簡單易懂)易得,時間複雜度 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

Prim

(不得不說,它長得很像 Dijkstra 的代碼)

代碼思路以下:

  1. 擬定任意一個點(一般取 1 號點)爲樹上的點,加入集合 T 中。
  2. 剩餘節點加入集合 S 中。
  3. 維護數組 d。當點 x 屬於集合 S 時(即它已被當作樹上的點):d[x]=x與 T 集合中節點之間權值最小的邊的權值。
  4. 不然當點 x 屬於集合 T 時:d[x]=x 被加入時選出的最小邊的權值。
  5. 沒了。

易得,時間複雜度: \(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 學習,我發現如下規律:

  1. 最小生成樹的題目一般很裸。
  2. 到後期也有 DP 基於最小生成樹實現。

四.我還有什麼可說的嗎

沒了

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息