这是一篇很长的文章。请原谅我。归结起来,问题是:是否有一个可行的就地基数排序算法?
我有大量的小型固定长度的字符串,只使用字母“a”,“C”,“G”和“T”(是的,你已经猜到了:DNA),我想要排序。
目前,我使用std::sort
,它在STL的所有常见实现中使用introsort。这工作得很好。然而,我确信基数排序完全适合我的问题集,并且应该在实践中更好地工作多。
我用一个非常简单的实现测试了这个假设,对于相对较小的输入(大约10,000),这是正确的(至少快两倍多)。然而,当问题规模变大(N > 5,000,000)时,运行时间会急剧下降。
原因很明显:基数排序需要复制整个数据(实际上在我的简单实现中不止一次)。这意味着我在主存中放置了~ 4 GiB,这显然会降低性能。即使它没有,我也不能使用这么多内存,因为问题的大小实际上会变得更大。
理想情况下,该算法应该适用于2到100之间的任何字符串长度,对于DNA和DNA5(允许额外的通配符“N”),甚至是具有IUPAC 歧义代码的DNA(导致16个不同的值)。然而,我意识到所有这些情况都无法涵盖,所以我对我得到的任何速度改进都很满意。代码可以动态地决定向哪个算法分派。
不幸的是,维基百科上关于基数排序的文章是无用的。关于原地变体的部分完全是垃圾。关于基数排序的NIST-DADS部分几乎不存在。有一篇听起来很有前途的论文叫做高效自适应就地基数排序,它描述了算法“MSL”。不幸的是,这篇论文也令人失望。
具体来说,有以下几点。
首先,该算法包含了一些错误,并留下了许多无法解释的地方。特别是,它没有详细说明递归调用(我只是假设它增加或减少一些指针来计算当前的移位和掩码值)。此外,它使用函数dest_group
和dest_address
,但没有给出定义。我不知道如何有效地实现这些(也就是说,在O(1);至少dest_address
不是微不足道的)。
最后但并非最不重要的是,该算法通过交换数组下标与输入数组中的元素来实现就地性。这显然只适用于数值数组。我需要把它用在绳子上。当然,我可以不使用强类型,假设内存可以容忍我将索引存储在不属于它的地方。但这只适用于我能把我的字符串压缩到32位内存(假设32位整数)。这只有16个字符(让我们暂时忽略16>日志(5,000,000))。
另一篇论文的作者没有给出准确的描述,但它给出了MSL的运行时是次线性的,这是完全错误的。
回顾一下:是否有任何希望找到一个工作参考实现或至少一个好的伪代码/描述的工作在DNA字符串上工作的就地基数排序?