图论及其应用——初步认识树


 

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

  所谓生成树,简单的来说就是对于连通分量为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);
    }
}


 

原文地址:https://www.cnblogs.com/rhythmic/p/5205702.html