一、说明

     POJ 1789这道题目看完题意之后,因为每个顶点到其他所有顶点都有边,所以这个问题显然是个稠密图,对于稠密图显然Prim更快,而且,如果细细分析之后,会发现Prim不仅仅是比Kruskal快哟,关键是省去了巨大内存空间。

     关于这个道题用Prim并且考虑内存节省的题解,参见我的博文:

     http://www.cnblogs.com/Oloo/articles/3631631.html

 二、Kruskal算法

     算法描述:

     将边按照升序排序,然后按照这个顺序依次取边,判断符不符合加入条件,如果符合,则把这条边的两个顶点合并成一个set,不符的话,取下一条边,重复上述过程。

     上面描述中的判断条件是,两个顶点是否位于一个set中。

 三、并查集的应用

     通过上面Kruskal算法描述,可以看出,我们有一个需要解决的问题——判断两个顶点是否属于同一个集合。

     这个就是并查集的典型应用。

     1、首先,我们想一下需要的操作,写出API

        MakeSet()//初始化

        Find(int u)//查找顶点u所在的集合标志,也就是下面说的集合的根节点

        Union(int u,int v)//将顶点u和顶点v所在的集合合并

     2、原理

        首先想一个最容易想到的方法

        那就是一个数组flag,执行makeSet(),将flag[i]=i 。每次执行Find(int u)的时候返回flag[u],这一步时间复杂性O(1)。每次执行Union(int u , int v)的时候,将所有和顶点u在同一集合的顶点的flag[i]置成flag[v]或者反过来,这一步时间复杂度O(n)

        下面做一下优化。

        考虑树型结构,我们将同一个集合的顶点连成树型结构,一个集合有一个根节点。我们不在设置上面的flag数组,而是设置root[]size[]两个数组。Root[i]表示i的父节点。在i点是一个集合的根节点的前提下,size[i]表示以i为节点的树型结构的节点数目。下面具体考虑Find()Union()的优化。

        Find()操作,因为root[i]储存i点的父节点,size[]要求根节点,所以,我们要求快速找到i所在树型结构的根节点,这里可以使用一个编程的trick,可以一边快一点找到根节点,一边将树扁平化。详情参见六程序实现中的Find方法。

        Union操作,我们的目标是尽量让构成的树型结构尽量扁平化,所以,当树i和树j合并的时候,我们要看一下他们的size[],将size较小的那棵树的根节点kroot[k] = size大的那棵树的根节点h,即root[k] = h。详情参见六程序实现中的Union方法。

        备注:博主比较懒散,只是当做记录一下,所以,说得可能不是太清楚,记得好像有个普林斯顿的算法公开课的第一节课就是讲并查集。<貌似主讲人是高德纳的徒弟哟..>

 四、问题描述

     POJ 1789http://poj.org/problem?id=1789

     卡车的牌照标识一般是从一种牌照演化到另一种,现在历史学家要研究牌照演化的进程,给定一组卡车牌照,写个程序帮历史学家找出牌照演化的最小生成树<详细见问题分析>。     

 五、问题分析

     编程之前需要解决两个问题:

     问题一、图的构造?

     问题二、Kruskal算法中的排序实现?

   

     问题一:这道题目图的构造还是不算难的!以每种卡车牌照为节点,任意一种牌照到其他牌照都有边,边的权重,通过比较两种牌照之间相同位置的不同字符个数。

     

     问题二:前面也提到了,博主比较懒散,所以,博主采用了#include<algorithm>中的sort的方法,我们要使用这个方法,只要重写一个cmp方法即可,详情参见程序实现中的cmp方法。关于stlsort方法效率的问题,参见:http://blog.sina.com.cn/s/blog_9d987af501014zlj.html。至此,这个题目使用Kruskal解决。

     

 六、程序实现

  1 #include<iostream>
  2 #include<string>
  3 #include <algorithm>
  4 using namespace std;
  5 
  6 struct edge
  7 {
  8     int u,v;
  9     int d;
 10 } edges[1999000];
 11 
 12 int N;//卡车编号的个数
 13 int root[2000];//每个set的根节点
 14 int size[2000];//每个set的大小
 15 char strs[2000][8];
 16 int edgeNum;//边的条数
 17 
 18 void makeSet()
 19 {
 20     for(int i = 0;i < N;i++)
 21     {
 22         root[i] = i;
 23         size[i] = 1;
 24     }
 25 }
 26 
 27 int Find(int i)
 28 {
 29     while(i != root[i])
 30     {
 31         root[i] = root[root[i]];
 32         i = root[i];
 33     }
 34     return i;
 35 }
 36 
 37 void Union(int u , int v)
 38 {
 39     int r1 = Find(u);
 40     int r2 = Find(v);
 41     if(size[r1] > size[r2])  {root[r2] = r1; size[r1] += size[r2];}
 42     else {root[r1] = r2; size[r2] += size[r1];}
 43 }
 44 
 45 int countD(int i , int j)
 46 {
 47     int sum = 0;
 48     for(int k = 0;k < 7;k++)
 49         if(strs[i][k] != strs[j][k])
 50             sum++;
 51     return sum;
 52 }
 53 
 54 void input()
 55 {
 56     makeSet();
 57     for(int i = 0;i < N;i++)
 58     {
 59         cin >> strs[i];
 60     }
 61     edgeNum = 0;
 62     for(int i = 0;i < N;i++)
 63     {
 64         for(int j = i+1;j < N;j++)
 65         {
 66                edges[edgeNum].u = i;
 67                edges[edgeNum].v = j;
 68                edges[edgeNum].d = countD(i,j);
 69                edgeNum++;
 70         }
 71     }
 72 }
 73 
 74 bool cmp(const edge & a, const edge & b)  
 75 {  
 76     return a.d < b.d;  
 77 }  
 78 
 79 int Kruskal()
 80 {
 81     //将边按照权重排序
 82     sort(edges, edges + edgeNum, cmp);
 83     int sum = 0;
 84     for(int i = 0;i < edgeNum;i++)
 85         if(Find(edges[i].u) != Find(edges[i].v))
 86         {
 87             Union(edges[i].u,edges[i].v);
 88             sum += edges[i].d;
 89         }
 90     return sum;
 91 }
 92 
 93 int main()
 94 {
 95     while(cin >> N)
 96     {
 97         if(N == 0) break;
 98         input();
 99         cout<<"The highest possible quality is 1/"<<Kruskal()<<"."<<endl;
100     }
101     return 0;
102 }
View Code

 七、出现问题

     问题1——MLE

     这是我第一次超内存。之前我一直不理解,为什么很多人做ACM题的时候,喜欢根据数据范围,直接声明全局数组。我之前都是用int *ss = new int[N]。但是动态申请占用堆空间,极容易MLE

     问题2——string

     这道题目计算每种牌照距离的时候,我第一次将strs[i]赋值给了一个临时的string变量。。这个弱智的地方导致我TLE

 八、感想

       自己通过自己独立分析问题,自己独立编程实现,最后这个题目AC,这个节奏还是很好的~

       最后,YZY,我想你!

 

 

 

 

 

 

posted on 2014-03-28 20:38  Oloo  阅读(192)  评论(0)    收藏  举报