对n长度为n的字符串进行排序的最快方法是什么?

时间:2014-06-27 02:49:56

标签: string algorithm sorting

我有n个字符串,每个字符串长度为n。我希望按升序排序。

我能想到的最好的算法是n ^ 2 log n,这是快速排序。 (比较两个字符串需要O(n)时间)。挑战是在O(n ^ 2)时间内完成。我该怎么办?

此外,不允许使用基数排序方法,因为您事先不知道字母表中的字母数。

5 个答案:

答案 0 :(得分:0)

对于所有情况解决它不应该更好O(N ^ 2 Log N)。 但是,如果存在可以放松字符串比较的约束,则可以对其进行优化。

- 如果字符串具有高重复率并且来自有限有序集。您可以使用计数排序中的想法并使用地图来存储其计数。以后,只需对地图键进行排序就足够了。 O(NMLogM)其中M是唯一字符串的数量。您甚至可以直接使用TreeMap来实现此目的。

- 如果字符串不是随机的,而是一些超级字符串的后缀,这很可能就完成了 O(N Log ^ 2N)。 http://discuss.codechef.com/questions/21385/a-tutorial-on-suffix-arrays

答案 1 :(得分:0)

假设任何字母是a到z。

由于不需要就地排序,因此创建一个长度为26的链表数组:

List[] sorted= new List[26]; // here each element is a list, where you can append 

对于该字符串中的字母,其排序位置是ascii:x - ' a'的差异。 例如,' c'是2,将被定位为

sorted[2].add('c')

这样,排序一个字符串只需要n。

所以对所有字符串进行排序需要n ^ 2。

例如,如果你有" zdcbacdca"。

z goes to sorted['z'-'a'].add('z'),
d goes to sorted['d'-'a'].add('d'),
....

排序后,一个可能的结果看起来像

0   1  2  3 ...  25  <br/>
a   b  c  d ...  z   <br/>
a   b  c             <br/>
       c

注意:字母集合的假设决定了排序数组的长度。

答案 2 :(得分:0)

这里最简单的方法是使用Bucket sort(n ^ 2)或Radix sort(kn)。

更高级的方法是使用Burstsort(n log n)。

答案 3 :(得分:0)

对于少量字符串,常规比较排序可能比这里的基数排序更快,因为基数排序需要的时间与存储每个字符所需的位数成比例。对于2字节的Unicode编码,并且对于相等的常数因子做出一些(公认的可疑的)假设,如果log2(n)>,则基数排序将更快。 16,即当排序超过约65,000个字符串时。

我还没有看到的一件事是,通过利用已知的公共前缀,可以增强比较类型的字符串。

假设我们的字符串是S [0],S [1],...,S [n-1]。让我们考虑使用最长公共前缀(LCP)表来扩充mergesort。首先,我们不是在内存中移动整个字符串,而是将索引列表操作为固定的字符串表。

每当我们合并两个排序的字符串索引列表X [0],...,X [k-1]和Y [0],...,Y [k-1]以产生Z [0]时, ...,Z [2k-1],我们还将获得2个LCP表(LCPX [0],...,LCPX [k-1]用于X和LCPY [0],...,LCPY [k]对于Y),我们需要生成LCPZ [0],...,LCPZ [2k-1]。 LCPX [i]给出了X [i]的最长前缀的长度,它也是X [i-1] 的前缀,LCPY和LCPZ也是如此。

S [X [0]]和S [Y [0]]之间的第一次比较不能使用LCP信息,我们需要完整的O(n)字符比较来确定结果。但在那之后,事情就会加速。

在第一次比较期间,在S [X [0]]和S [Y [0]]之间,我们还可以计算其LCP的长度 - 调用L.将Z [0]设置为S中的任何一个[ X [0]]和S [Y [0]]比较小,并设置LCPZ [0] = 0.我们将L保持最近比较的LCP长度。我们还将在M中记录最后一个“比较失败者”与其块中的下一个字符串共享的LCP长度:即,如果是最近的比较,则在两个字符串S [X [i]]和S [Y之间[j]],确定S [X [i]]较小,则M = LCPX [i + 1],否则M = LCPY [j + 1]。

基本思路是:在任何合并步骤中进行第一次字符串比较后, S [X [i]]和S [Y [j]]之间的每个剩余字符串比较都可以从L和M的最小值开始而不是0。那是因为我们知道S [X [i]]和S [Y [j]]必须在开始时至少同意这么多字符,所以我们不需要打扰比较它们。随着越来越大的排序字符串块的形成,块中的相邻字符串将倾向于以更长的公共前缀开始,因此这些LCP值将变得更大,从而消除了越来越多的无意义字符比较。

在S [X [i]]和S [Y [j]]之间的每次比较之后,“失败者”的字符串索引像往常一样被附加到Z.计算相应的LCPZ值很容易:如果最后2个输家都来自X,则取LCPX [i];如果他们都来自Y,请采取LCPY [j];如果它们来自不同的块,则取前一个L值。

事实上,我们可以做得更好。假设最后的比较发现S [X [i]]&lt; S [Y [j]],使得X [i]是最近附加到Z的字符串索引。如果M(= LCPX [i + 1])&gt; L,那么我们已经知道S [X [i + 1]]&lt; S [Y [j]]甚至没有进行任何比较!这是因为要达到我们当前的状态,我们知道S [X [i]]和S [Y [j]]必须首先在字符位置L处有所不同,并且它必须是S中此位置的字符x [X [i]]小于S [Y [j]]中该位置的字符y,因为我们得出S [X [i]]&lt; S [Y [j]] - 所以如果S [X [i + 1]]与S [X [i]]共享至少第一个L + 1个字符,它还必须在L位置包含x,所以它也必须比S [Y [j]]小。 (当然情况是对称的:如果最后一次比较发现S [Y [j]]&lt; S [X [i]],只需交换周围的名字。)

我不知道这是否会将O(n ^ 2 log n)的复杂性提高到更好的程度,但它应该有所帮助。

答案 4 :(得分:0)

你可以建立一个Trie,费用为O(s * n),

详细说明: https://stackoverflow.com/a/13109908

相关问题