最小生成树

最小生成树

基础图论算法,今年(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 基于最小生成树实现。

四.我还有什么可说的吗

没了