【问题标题】:What is the fastest way to sort n strings of length n each?对每个长度为 n 的 n 个字符串进行排序的最快方法是什么?
【发布时间】:2014-08-18 01:18:33
【问题描述】:

我有 n 个字符串,每个长度为 n。我希望按升序对它们进行排序。

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

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

【问题讨论】:

  • 没有限制,所以我认为我们可以假设 10^4 或更大
  • 嗯,你可以通过字符串中的N^2个字母来统计字母表中的字母个数(只需要O(N^2)时间),然后使用基数排序。 ..
  • 我们可以认为是Unicode,65536个字符
  • @T.C.你打算如何计算 O(n^2) 时间内的数字?

标签: string algorithm sorting


【解决方案1】:

您可以构建一个Trie,这将花费 O(s*n),

详情: https://stackoverflow.com/a/13109908

【讨论】:

    【解决方案2】:

    对于少量字符串,常规比较排序可能比基数排序更快,因为基数排序所花费的时间与存储每个字符所需的位数成正比。对于 2 字节的 Unicode 编码,并且对相等的常数因子做出一些(诚然可疑的)假设,基数排序只有在 log2(n) > 16 时才会更快,即在对超过大约 65,000 个字符串进行排序时。

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

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

    每当我们合并两个字符串索引的排序列表 X[0], ..., X[k-1] 和 Y[0], ..., Y[k-1] 以产生 Z[0], ..., Z[2k-1],我们还将获得 2 个 LCP 表(LCPX[0], ..., LCPX[k-1] for X 和 LCPY[0], ..., LCPY[k -1] 对于 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]] 每次比较之后,“loser”的字符串索引像往常一样附加到 Z。计算对应的 LCPZ 值很简单:如果最后 2 个失败者都来自 X,则取 LCPX[i];如果他们都来自 Y,取 LCPY[j];如果它们来自不同的块,则取之前的 L 值。

    事实上,我们可以做得更好。假设最后比较发现 S[X[i]] L,那么我们已经知道 S[X[i+1]]

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

    【讨论】:

      【解决方案3】:

      假设任何字母都是 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
      

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

      【讨论】:

      • "zdcbacdca" 只是一个字符串,我想你误解了这个问题
      • 你想把所有字符串按升序排列在一起吗?!
      • 所以,例如,我们有一个输入是 zzz、abc、bcd、acd,对吗?并且预期的输出是 abc, acd, bcd, zzz :)
      【解决方案4】:

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

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

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

      【讨论】:

        猜你喜欢
        • 2019-01-18
        • 2016-08-28
        • 2015-12-13
        • 2011-05-24
        • 1970-01-01
        • 2011-01-18
        • 1970-01-01
        • 1970-01-01
        • 2021-03-12
        相关资源
        最近更新 更多