一、说明
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较小的那棵树的根节点k的root[k] = size大的那棵树的根节点h,即root[k] = h。详情参见六程序实现中的Union方法。
备注:博主比较懒散,只是当做记录一下,所以,说得可能不是太清楚,记得好像有个普林斯顿的算法公开课的第一节课就是讲并查集。<貌似主讲人是高德纳的徒弟哟..>
四、问题描述
POJ 1789:http://poj.org/problem?id=1789
卡车的牌照标识一般是从一种牌照演化到另一种,现在历史学家要研究牌照演化的进程,给定一组卡车牌照,写个程序帮历史学家找出牌照演化的最小生成树<详细见问题分析>。
五、问题分析
编程之前需要解决两个问题:
问题一、图的构造?
问题二、Kruskal算法中的排序实现?
问题一:这道题目图的构造还是不算难的!以每种卡车牌照为节点,任意一种牌照到其他牌照都有边,边的权重,通过比较两种牌照之间相同位置的不同字符个数。
问题二:前面也提到了,博主比较懒散,所以,博主采用了#include<algorithm>中的sort的方法,我们要使用这个方法,只要重写一个cmp方法即可,详情参见程序实现中的cmp方法。关于stl中sort方法效率的问题,参见: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 }
七、出现问题
问题1——MLE:
这是我第一次超内存。之前我一直不理解,为什么很多人做ACM题的时候,喜欢根据数据范围,直接声明全局数组。我之前都是用int *s,s = new int[N]。但是动态申请占用堆空间,极容易MLE。
问题2——string:
这道题目计算每种牌照距离的时候,我第一次将strs[i]赋值给了一个临时的string变量。。这个弱智的地方导致我TLE。
八、感想
自己通过自己独立分析问题,自己独立编程实现,最后这个题目AC,这个节奏还是很好的~
最后,YZY,我想你!~
浙公网安备 33010602011771号