对于少量字符串,常规比较排序可能比基数排序更快,因为基数排序所花费的时间与存储每个字符所需的位数成正比。对于 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) 提高到更好的水平,但它应该会有所帮助。