• 最小生成树


    写在前面:我是一只蒟蒻。

    好的,今天我们来讲一讲“最小生成树”。

    首先,我们来看看什么是最小生成树,

    定义:

    一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。(摘自百度百科)

    定理:任意一棵最小生成树一定包含无向图中权值最小的边。

    证明:

    反证法。假设无向图G=(V,E)存在一棵最小生成树不包含权值最小的边。设边e(x,y,z)是无向图中权值最小的边。把e加入到树中,e会和树上的x,y构成一个环,且e比环上任意一条边要小,所以用e来替换环上的任意一条边,就会得到比原树更小的生成树,与结论矛盾,所以此结论成立。

    对于这样的一个问题,我们有什么算法来解决呢?

    下面,我就给大家来介绍一下解决这类问题的两种算法:

    kruskal算法

    首先,我们从先从定义来看,要找最小生成树,就要先找边权最短,所以我们就可以先排序(以边权值从小到大),然后再寻找两边能否使图连通,如果可以就加入这棵树,这时我们就需要使用并查集来维护。

    下面是伪代码流程:

    1、首先我们先用并查集将每个点的祖先初始化

    2、排序

    3、扫描每条边,判断是否是联通两点的最短边

    下面是图形的演示

       

     

     

    代码:

     1 #include<bits/stdc++.h>
     2 #define N 200007
     3 #define maxn 5007
     4 using namespace std;
     5 struct edge{
     6     int x,w,y;
     7 }e[N*2];
     8 int head[maxn],tot,num,ans,fa[maxn],n,m;
     9 bool cmp(edge a,edge b){
    10     return a.w<b.w;
    11 }
    12 int find(int x){
    13     if(x==fa[x])return x;
    14     fa[x]=find(fa[x]);
    15     return fa[x];
    16 }
    17 void kruskal(){
    18     for(int i=1;i<=n;i++)fa[i]=i;
    19     for(int i=1;i<=m;i++){
    20         int x=e[i].x,y=e[i].y;
    21         if(find(x)==find(y))continue;
    22         fa[find(x)]=find(y);
    23         ans+=e[i].w;
    24         num++;
    25         if(num==n-1)break;
    26     }
    27 }
    28 
    29 int main(){
    30     scanf("%d%d",&n,&m);
    31     for(int i=1;i<=m;i++){
    32         scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w);
    33     }
    34     sort(e+1,e+1+m,cmp);
    35     kruskal();
    36     printf("%d
    ",ans);
    37     return 0;
    38 }

     看完模板代码,我们来看一道板子题:洛谷P1546

    题目:

    题目背景

    农民约翰被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。

    题目描述

    约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。

    你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过100000

    输入输出格式

    输入格式:

    第一行: 农场的个数,N(3<=N<=100)。

    第二行..结尾: 后来的行包含了一个N*N的矩阵,表示每个农场之间的距离。理论上,他们是N行,每行由N个用空格分隔的数组成,实际上,他们限制在80个字符,因此,某些行会紧接着另一些行。当然,对角线将会是0,因为不会有线路从第i个农场到它本身。

    输出格式:

    只有一个输出,其中包含连接到每个农场的光纤的最小长度。

    输入输出样例

    输入样例#1:
    4
    0 4 9 21
    4 0 8 17
    9 8 0 16
    21 17 16 0
    输出样例#1:
    28

    这道题,我们读完题就可以十分十分简单的看出这道题是道图论(废话,不说我也能看出来 )。。。。。。

    再一看,我们又看到我们就要建树,为了满足题意的条件,我们就要找最小生成树。

    nice!下一道。(本题AC代码放置博客最后)

     洛谷P2330

    题目:

    题目描述

    城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造。城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条道路相连接。这些道路是双向的,且把所有的交叉路口直接或间接的连接起来了。每条道路都有一个分值,分值越小表示这个道路越繁忙,越需要进行改造。但是市政府的资金有限,市长希望进行改造的道路越少越好,于是他提出下面的要求:

    1.改造的那些道路能够把所有的交叉路口直接或间接的连通起来。 2.在满足要求1的情况下,改造的道路尽量少。 3.在满足要求1、2的情况下,改造的那些道路中分值最大的道路分值尽量小。

    任务:作为市规划局的你,应当作出最佳的决策,选择那些道路应当被修建。

    输入输出格式

    输入格式:

    第一行有两个整数n,m表示城市有n个交叉路口,m条道路。

    接下来m行是对每条道路的描述,u, v, c表示交叉路口u和v之间有道路相连,分值为c。(1≤n≤300,1≤c≤10000,1≤m≤100000)

    输出格式:

    两个整数s, max,表示你选出了几条道路,分值最大的那条道路的分值是多少。

    输入输出样例

    输入样例#1:
    4 5
    1 2 3
    1 4 5
    2 4 7
    2 3 6
    3 4 8
    
    输出样例#1:
    3 6

    这道题在上一道题上又多了一个新的概念“瓶颈生成树”,什么是瓶颈生成树,很简单,我们来看一下它的定义:
    无向图G的一颗瓶颈生成树是这样的一颗生成树T,它最大的边权值在G的所有生成树中是最小的。瓶颈生成树的值为T中最大权值边的。

    一个结论:无向图的最小生成树一定是瓶颈生成树,但瓶颈生成树不一定是最小生成树。

    其正确性就不在此证明了,这道题就是一颗瓶颈生成树,我们只需要在找最小生成树时统计一下最小边加的边数,最后输出就可以了。


    6月26日更新

    非常抱歉,当时在写完这部分之后就忘了还缺内容,今天在做到最小生成树的题的时候,才想起来。

    补得代码:

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define maxn 200002
     4 struct edge{
     5     int x,y,w;
     6 }e[maxn*2];
     7 int head[maxn],tot,n,ans,p=1,fa[maxn];
     8 void add(int a,int b,int c){
     9     e[++tot].w=c;
    10     e[tot].x=a;
    11     e[tot].y=b;
    12 }
    13 int cmp(edge a,edge b){
    14     return a.w<b.w;
    15 }
    16 int find(int x){
    17     if(fa[x]==x)return x;
    18     fa[x]=find(fa[x]);
    19     return fa[x];
    20 }
    21 void kruskal(){
    22     for(int i=1;i<=n;i++)fa[i]=i;
    23     for(int i=1;i<=tot;i++){
    24         int x=e[i].x,y=e[i].y;
    25         if(find(x)==find(y))continue;
    26         fa[find(x)]=find(y);
    27         ans+=e[i].w;
    28         p++;
    29         if(p==n)break;
    30     }
    31 }
    32 
    33 
    34 int main(){
    35     scanf("%d",&n);
    36     for(int i=1;i<=n;i++){
    37         for(int j=1;j<=n;j++){
    38             int a;
    39             scanf("%d",&a);
    40             if(i<j){
    41                 add(i,j,a);
    42                 add(j,i,a);
    43             }
    44         }
    45     }
    46     sort(e+1,e+1+tot,cmp);
    47     kruskal();
    48     printf("%d
    ",ans);
    49 
    50 
    51     return 0;
    52 }
    P1546
     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define maxn 100007
     4 #define N 308
     5 struct edge{
     6     int x,y,w;
     7 }e[maxn*2];
     8 int head[N],tot,ans,maxx=0,fa[N],n,m;
     9 void add(int a,int b,int c){
    10     e[++tot].x=a;
    11     e[tot].w=c;
    12     e[tot].y=b;
    13 }
    14 int cmp(edge a,edge b){
    15     return a.w<b.w;
    16 }
    17 int find(int x){
    18     if(fa[x]==x)return fa[x];
    19     fa[x]=find(fa[x]);
    20     return fa[x];
    21 }
    22 void kruskal(){
    23     for(int i=1;i<=n;i++)fa[i]=i;
    24     for(int i=1;i<=tot;i++){
    25         int x=find(e[i].x);
    26         int y=find(e[i].y);
    27         if(x==y)continue;
    28         fa[x]=y;
    29         ans++;
    30         maxx=max(e[i].w,maxx);
    31     }
    32 }
    33 int main(){
    34     scanf("%d%d",&n,&m);
    35     for(int i=1;i<=m;i++){
    36         int a,b,c;
    37         scanf("%d%d%d",&a,&b,&c);
    38         add(a,b,c);
    39         add(b,a,c);
    40     }
    41     sort(e+1,e+1+tot,cmp);
    42     kruskal();
    43     printf("%d %d
    ",ans,maxx);
    44     return 0;
    45 }
    P2330

    下面进入主题:

    好久之前我们介绍了kruskal算法求最小生成树

     今天,我们来看一看

    Prim算法

    Prim算法与kruskal算法的核心思想是一样的,但是思路和操作的方式有些不同,Prim总是维护最小生成树的一部分。在最开始,其只是确定最小生成树的一个点。

    操作流程:

    ① 先建立一个只有一个结点的树,这个结点可以是原图中任
    意的一个结点。
    ② 使用一条边扩展这个树,要求这条边一个顶点在树中另一
    个顶点不在树中,并且这条边的权值要求最小。
    ③ 重复步骤②直到所有顶点都在树中。

    模板:

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define N 5010
     4 int a[N][N],d[N],n,m,ans;
     5 bool vis[N];//指示该点是否在最小生成树中
     6 void prim(){
     7     memset(d,0x3f3f3f3f,sizeof(d));
     8     memset(vis,0,sizeof(vis));
     9     d[1]=0;//初始点
    10     for(int i=1;i<n;i++){
    11         int x=0;
    12         for(int j=1;j<=n;j++){
    13             if(!vis[j]&&d[j]<d[x])x=j;
    14         }
    15         vis[x]=1;//加入树中
    16         for(int j=1;j<=n;j++){
    17             if(!vis[j])d[j]=min(d[j],a[x][j]);
    18         }
    19     }
    20 }
    21 
    22 
    23 int main(){
    24     scanf("%d%d",&n,&m);
    25     memset(a,0x3f3f3f3f,sizeof(a));
    26     for(int i=1;i<=n;i++){
    27         a[i][i]=0;
    28     }
    29     for(int i=1;i<=m;i++){
    30         int x,y,z;
    31         scanf("%d%d%d",&x,&y,&z);
    32         a[y][x]=a[x][y]=min(a[x][y],z);
    33     }
    34     prim();
    35     for(int i=2;i<=n;i++)ans+=d[i];
    36     printf("%d",ans);
    37 
    38 
    39 
    40     return 0;
    41 }

    Prim 主要用于稠密图,完全图。

    下面给大家推荐一道题

    洛谷P1265

    代码(前面的kruskal。。。是错的):

      1 /*#include<bits/stdc++.h>
      2 using namespace std;
      3 struct node{
      4     int x,y;
      5     double w;
      6 }e[10007];
      7 double calc(int x,int y,int xx,int yy){
      8     return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));//求两点距离
      9 }
     10 int head[5007],fa[5007],n,cnt,dis[5007][5007];
     11 struct NODE{
     12     int x,y;
     13 }a[5007];
     14 int find(int x){
     15     if(fa[x]==x)return fa[x];
     16     fa[x]=find(fa[x]);
     17     return fa[x];
     18 }
     19 bool cmp(node a,node b){
     20     return a.w<b.w;
     21 }
     22 
     23 void add(int a,int b,double c){
     24     e[++cnt].x=a;
     25     e[cnt].y=b;
     26     e[cnt].w=c;
     27 }
     28 
     29 // double 
     30 
     31 
     32 int main(){
     33     scanf("%d",&n);
     34     for(int i=1;i<=n;i++){
     35         // int a,b;
     36         scanf("%d%d",&a[i].x,&a[i].y);
     37     }
     38     for(int i=1;i<=n;i++){
     39         for(int j=1;j<=n;j++){
     40             if(i==j)continue;
     41             add(i,j,calc(a[i].x,a[i].y,a[j].x,a[j].y));
     42             fa[i]=i;
     43         }
     44     }
     45     double ans=0;
     46     sort(e+1,e+1+cnt,cmp);
     47     for(int i=1;i<=cnt;i++){
     48         int x=e[i].x;
     49         int y=e[i].y;
     50         int f1=find(x);
     51         int f2=find(y);
     52         if(f1==f2){
     53             continue;
     54         }
     55         fa[f1]=f2;
     56         ans+=e[i].w;
     57     }
     58     printf("%0.2f",ans);
     59     return 0;
     60 }*///kruskal
     61 //prim算法
     62 #include<bits/stdc++.h>
     63 using namespace std;
     64 #define N 5007
     65 #define INF 100000000
     66 template <typename type>
     67 void scan(type &x){
     68     type f=1;x=0;char s=getchar();
     69     while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
     70     while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
     71     x*=f;
     72 }
     73 struct node{
     74     int x,y;
     75 }a[N];
     76 double dis[N],ans;
     77 bool vis[N];//记录数组
     78 int n;
     79 double calc(int x,int y,int xx,int yy){
     80     return sqrt((double)(x-xx)*(x-xx)+(double)(y-yy)*(y-yy));//求两点距离
     81 }
     82 int main(){
     83     scan(n);
     84     for(int i=1;i<=n;i++){
     85         scan(a[i].x);
     86         scan(a[i].y);
     87         dis[i]=INF;
     88     }
     89     dis[1]=0;
     90     int k;
     91     for(int i=1;i<=n;i++){
     92         double minn=INF;
     93         for(int j=1;j<=n;j++){
     94          if(!vis[j]&&dis[j]<minn){
     95                 minn=dis[j];
     96                 k=j;//取最优点
     97             }
     98         }
     99         ans+=minn;vis[k]=1;//最优点加入树中
    100         for(int j=1;j<=n;j++){
    101             double d=calc(a[k].x,a[k].y,a[j].x,a[j].y);
    102             if(dis[j]>d) dis[j]=d;
    103          }
    104     }
    105     printf("%.2lf",ans);
    106     return 0;
    107 }
    P1265
  • 相关阅读:
    火狐优化及遇到的问题
    拷贝工程,名字不改变问题
    Ajax基础实例
    Java中检测字符串的编码格式
    innerHTML和outerHTML有什么区别
    启动Tomcat出现“Bad version number in .class file (unable to load class XXX)”解决
    MyEclipse8.6安装SVN 教程 与遇到的问题
    彻底卸载JDK的-并只依赖配置环境安装JDK(不依赖注册表)-解决Error opening registry key'softwareJavasoftJava Runti问题
    数据库下的分页代码
    WSGI
  • 原文地址:https://www.cnblogs.com/xishirujin/p/10432862.html
Copyright © 2020-2023  润新知