zoukankan      html  css  js  c++  java
  • 图论及其应用——初步认识树


     

      基于在《图论及其应用——图》中对于并查集简单的了解,这里我们将继续讨论一下关于“树”的问题。首先这里引入一些概念。  

      所谓生成树,简单的来说就是对于连通分量为1的Graph图——G(V,E),我们找到一个G的子图T(V',E'),这里V = V',E' 则可以是E的子集。  

      那对应的生成森林,就是连通分量为n的Graph图,我们分别分析每一个连通分量,分别得到生成树,最后组在一起就成了生成森林。  

      而所谓的最小生成树,就是如果给定的Graph图——G(V,E)的点集E是带权值的(即这个边上存着数值),在所有生成树中,权值最小的那个即是最小生成树。  

      以上的定义显得很抽象,我们结合具体的问题来看就会更加明白。  

      那我们就拿来一个具体的例子来分析。(Problem source : hdu 1301)   

     

      题目大意:通过键盘输入给你一个Graph图——G(V,E),其中边集E是带权值的(就说连接点之间的边上储存着数值),现在你找到一个子图,要求这个子图能够连通原图中的所有点(也就是说这个图首先是原图的生成树),并且这个生成树的权值是所有生成树中最小的,即最小生成树。   数理分析:关于生成最小生成树的算法,常见的有prim算法和Kruskal算法,这里我们将介绍Kruskal算法。  

      它的具体步骤如下。  

      1.我们设G(V,E)是原图(点集V含有n个元素),G'(V',E')是最小生成树。我们先将带权值的边集E进行排序。  

      2.我们先生成G'(V,∅),然后从权值最小的边开始尝试构建最小生成树G',如果添加当前这条最小边导致生成了环,则弃掉当前边。  

      3.遍历所有的边,如果能够选出n - 1条,则说明得到了最小生成树,否则的话,只能生成最小森林。

      Kruskal算法基于贪心的思想。为了得到生成树,我们要在边集E中选出n - 1个元素,这是保证含有n个元素的点集连通的最小边数,那么我们只需从最小的带权边开始构造即可。这个构造过程中,如果出现了某个边使生成树出现了环,那么我们至少需要n条边才能构造出生成树(这显而易见)。此时我们有两个选择,一种选择是,弃掉这条边,我们构造n-1个元素的边集V',另一种选择是,拿这条边,构造n个元素的边集V'。我们简单的进行比较就能够的出答案。   E = {E1,E2,E3,E4,E5,E6}(按权值的递增排列)这里我们假设E3处生成环。  

      方案一:E1,E2,E4,E5。  

      方案二:E1,E2,E3,E4,E5,E6.(因为选择了导致构成环的E3对生成树的连通性没有影响,所以E3后面的点和方案一完全一样,但是要多一个元素)   显而易见,方案一才是运用了贪心思想的最优构造方法。   以上就是生成最小生成树的Kruskal算法。

      编程实现:有了以上对Kruskal算法的理解再加上对并查集的理解,我们在编程实现上并不难实现它。   值得一提的是,在并查集和最小生成树一类的问题当中,往往是把本质上是无向的Graph图当做有向图来处理,这里主要是为了编程实现的方便。  

      参考代码如下。
     

    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int root[30];
    
    int Find(int t)
    {
        if(root[t] == t)
              return t;
        else
              return root[t] = Find(root[t]);
    }
    
    void bing(int x,int y)
    {
         if(Find(x) != Find(y))
         {
             if(Find(x) < Find(y))
                 root[Find(y)] = Find(x);
             else
                 root[Find(x)] = Find(y);
         }
    }
    int main()
    {
        int n,i,j,k,x,y,Min,sum,cost[30][30];
        char ch1,ch2;
        while(scanf("%d",&n) , n)
        {
             getchar();
             memset(cost , -1 , sizeof(cost));
            for(i = 1;i < n;i++)
            {
                 root[i] = i;
             scanf("%c %d",&ch1,&k);
               for(j = 1;j <= k;j++)
                {
                  scanf(" %c %d",&ch2,&x);
                  cost[i][ch2 - 'A' + 1] = x;
                }
                getchar();
            }
        root[n] = n;
        sum = 0;
        for(k = 1;k < n;k++)
        {
              Min = 100000;
                  for(i = 1;i < n;i++)
                  {
                       for(j = i + 1;j <= n;j++)
                       {
                            if(cost[i][j] > 0 && Find(i) != Find(j) && cost[i][j] < Min)
                            {
                                 Min = cost[i][j];
                                 x = i , y = j;
                            }
                       }
                  }
                  bing(x , y);
                  sum += Min;
        }
        printf("%d
    ",sum);
        }
        return 0;
    
    }

      我们不妨再看一道关于最小生成树的题目。(Problem source : hdu1233)

        编程实现:还是典型的最小生成树问题,我们依然用Kruskal算法来实现,但是我们这次基于结构体来记录Graph图的信息。
        我们构造带权边集E的结构体,含有三个变量,分别是这条边的起点preV,终点endV,权重weigh。这样用边集E记录图的好处在于,对边的权进行排序非常方便。
        随后我们只需按照Kruskal算法给出的步骤编程实现即可。


        参考代码如下。

    #include<stdio.h>
    #include<algorithm>
    using namespace std;
    const int maxn1 = 5000;
    const int maxn2 = 105;
    int father[maxn2];
    struct edge
    {
        int weigh;
        int preV;
        int endV;
    }edges[maxn1];
    
    bool cmp(edge a , edge b)
    {
         return a.weigh < b.weigh ? 1 : 0;
    }
    int Find(int x)
    {
         if(father[x] == x)  return x;
         else                return father[x] = Find(father[x]);
    }
    
    int Kruskal(int n , int es)
    {
        int sum = 0;
        for(int i = 1;i <= n;i++)
             father[i] = i;
        sort(edges+1,edges + 1 + es,cmp);
        for(int i = 1;i <= es;i++)
        {
             int fx = Find(edges[i].preV);
             int fy = Find(edges[i].endV);
             if(fx != fy)
             {
                  father[fx] = fy;
                  sum += edges[i].weigh;
             }
        }
        return sum;
    }
    int main()
    {
        int n;
        int i , es;
        while(scanf("%d",&n) , n)
        {
             es = (n-1)*n*0.5;
              for(i = 1;i <= es;i++)
              {
                   scanf("%d %d %d",&edges[i].preV,&edges[i].endV,&edges[i].weigh);
              }
              int ans = Kruskal(n,es);
              printf("%d
    ",ans);
        }
    }


     

  • 相关阅读:
    第三次作业
    C语言第二次作业
    第一次作业
    C语言第六次作业
    C语言第五次作业
    C语言第四次作业
    C语言第三次作业
    C语言第二次作业
    C语言第一次作业
    C语言学习笔记(6)
  • 原文地址:https://www.cnblogs.com/rhythmic/p/5205702.html
Copyright © 2011-2022 走看看