单词排序
2012-10-08 19:18 ggzwtj 阅读(1929) 评论(0) 编辑 收藏 举报这个是内网的一个编程比赛,我最终没有取得成绩,但还是学了不少的东西,还是在这里写一下大家的思路吧,欢迎讨论。
第一种做法:暴力排序
很显然,最简单的方法就是单线程将文件读入,然后排序。
因为字符串比较的时候效率不是很高,所以耗时还是比较长的,下面开始优化。
第二种做法:桶排序
第一种做法的效率不高的原因,不只是因为单线程。试想一下,随着排序的进行整体上都逐渐地变得有序,那么如果再比较相邻两个位置的单词的时候就会逐个地比较每个字母。而且再细想一下,这些字母在之前的过程中已经过了,所以就可以考虑桶排序了,每个字母都只会被比较一次。这样效率有一点提升了。
第三种做法:压缩字母的桶排序
在第二种做法中,桶的大小是26(因为单词中有26个字符),构造桶的复杂度当然很低,遍历一次所有的字符即可,但是在排序遍历的次数则是所有单词中最长的长度(设为N)。那么如果我们把两个字符看做一个“字符”,那么桶的大小就变成了26*26,而遍历次数就变成了N/2,实际结果效率有一些提高。当然也可以考虑将3个字符看成一个整体。
第四种做法:TRIE
TRIE绝对算是字符串处理中的常客,典型的用空间换时间。具体排序的过程是:边读文件边创建TRIE,完成后,边遍历TRIE边输出。看似这种做法的效率应该是最高的,甚至比第三种做法的效率还高,但实际的结果并不是如此,感觉原因应该是大量的内存的随机访问。
有了上面四种做法,我们先暂停一下,先来分析一下各种做法之间的区别。上面的分析主要是想从算法的角度去优化效率,但是再找到理论下限的做法的时候效率仍然不好,我们就应该换个角度来看这个问题了。
- 暴力排序的复杂度O(NKlogN),其中N是单词的数目,K是平均比较的长度。在比赛中N为270000左右,那么logN大约也就是10左右,总的大概为:2700000*K。
- 第二、三的复杂度为O(NlogN),总的也就是2700000。
- 第四种的复杂度为O(M),M为字符数目,比赛中大概为3000000。
可以看到,虽然第四种算法上是最优的,但是实际上的总的复杂度是差不多的,那么提高效率的关键就是具体的实现,而不是算法。这里就体现出来C和java的区别了,C可以把数组中连续的4个char变成一个int,而java就无法做到。那么,
第五种做法:用两个Int64表示(利用了所有的单词不超过16)
读进来char[],然后创建Struct(包含两个Int64)来表示一个字符串,那么在比较的时候只需要比较这两个Int64即可,而且大多数情况下比较第一个Int64的时候就已经知道结果了,而且实际测试中发现随机生成大量的Int64和Struct,两个比较的效率相差不大。
第六种做法:用一个Int64表示
其实,26种字符可以用5个位来表示,那么一个Int64可以表示12个字符,那么其余的字符怎么办呢?声明Int64[26][26][26][26][MAXN]的数组就可以了,用5个位表示的目的是用位移操作来代替乘法,然将各个数组排序即可。
下面来考虑用多线线程来进一步提高效率,两种方法:
- 多线程将各个段排序,然后合并成一个;
- 先根据前几个字符分组,然后排序,那么各个线程已经是有序的了;
大家也许认为第二种方法有一些优势,但实际上差别不大,尤其是在这种小的数据量(很容易实现100MS以下)的情况下,最最关键的还是具体程序的实现。
--------------------------------------
欢迎拍砖。