“就地”MSD 基数排序、堆栈空间和堆栈溢出

Posted

技术标签:

【中文标题】“就地”MSD 基数排序、堆栈空间和堆栈溢出【英文标题】:"In-place" MSD radix sort, stack space, and Stack Overflow's 【发布时间】:2013-11-09 09:36:59 【问题描述】:

我真的很困惑the "in-place" MSD radix sort算法:

然后使用下一个数字递归处理每个 bin,直到所有数字都用于排序。

我很困惑,因为在我看来递归意味着 O(n) 堆栈空间,其中 n 是最长字符串的长度(以位数为单位),对吧?

在我看来,避免堆栈溢出的唯一方法是使用堆空间——但是从任何定义来看,算法都不再是“就地”了。

那么,如何就地进行 MSD 基数排序?

【问题讨论】:

考虑在cs.stackexchange.com上询问与cs相关的问题。 @RealzSlaw:我完全忘记了那个网站,谢谢。 【参考方案1】:

我认为术语“就地 MSD 基数排序”有点误导,因为正如您所指出的,它不是严格定义“就地”的就地算法。这里的“就地”术语很可能是指这样一个事实,即与 LSD 基数排序不同,该算法不需要辅助数组来临时存储原始输入数组中的元素。

MSD 基数排序的空间使用量与最大输入数的位数成正比是正确的。为了符号简单,我们让 n 是输入数组的长度,U 是数组中的最大数。 MSD 基数排序的运行时间为 O(n log U),因为数字 U 的位数为 O(log U)。 O(log U) 是一个非常非常缓慢增长的函数。作为参考,宇宙中的原子数大约是1080,也就是大约2240。因此,如果您对任何物理过程生成的数字进行排序,递归深度最多为 240,虽然很大,但绝对可以管理。

如果您要对 非常 大的数字进行排序 - 例如,具有数千位和数千位的数字 - 那么您担心堆栈溢出是正确的。但是,我认为如果您有一个良好的 MSD 基数排序实现,这种情况发生的可能性极小。快速排序中有一个标准优化——看起来很像 MSD 基数排序——不是进行两个分支递归调用,而是对两个范围中较小的一个进行递归调用以进行排序,然后从初始调用中回收堆栈帧排序更大的范围。 (这本质上是一个尾调用消除)。现在,假设您将此应用于 MSD 基数排序。由于每个新创建的堆栈帧都在要排序的两个范围中的较小者上工作,因此您可以保证每个新堆栈帧中的元素数量是前一个堆栈帧的一半。因此,堆栈可以达到的最大深度是 O(log n) - not O(log U)。为了让你的堆栈爆炸,你需要一个真正天文数字的大输入数组,不管堆栈大小。

总而言之:你是对的,算法没有到位。但是,由于堆栈深度在简单实现中为 O(log U) 而在优化实现中为 O(log n),因此除非您有一个简单的实现并且确实非常巨大,否则您不必担心这一点输入。

【讨论】:

+1 谢谢。请注意,这不一定仅适用于数字本身;字符串可以任意长。 (提到这个只是因为你提到了宇宙中的原子数量。)【参考方案2】:

这个算法是就地的,因为它交换了来自数组两端的两个值。一个例子是:

110,010,111,000,011

0 bin 放在左边,而 1 bin 放在右边。从 MSD 开始,逐步排序如下:

|110,010,111,000,010|
011|010,111,000|110
011,010|111,000|110
011,010,000||111,110

在这个简化为 O(n) 的示例中,这可以在 O(3n) 时间内完成。唯一需要的额外内存是足够大的交换空间以容纳一个元素。

【讨论】:

以上是关于“就地”MSD 基数排序、堆栈空间和堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章

字符串算法—字符串排序(下篇)

基数排序-LSD&MSD

数据结构Java版之基数排序

排序算法——基数排序

MSD 与 LSD 基数排序

桶排序和基数排序有啥区别?