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