面试难题:用有限的内存对一百万个数字输入进行排序

Posted

技术标签:

【中文标题】面试难题:用有限的内存对一百万个数字输入进行排序【英文标题】:Interview puzzle: Sorting a million number input with limited memory 【发布时间】:2012-11-26 09:08:26 【问题描述】:

我尝试使用外部排序来回答这个问题,但面试官回答说复杂度很高 n.n(log(n)) 即 n 平方 *logn。 有没有更好的选择。

为了简化问题: 让我们假设我们有 1000 个元素要排序,并且仅分配给 100 个元素的空间。比外部排序花费更少时间的最佳算法是什么。

【问题讨论】:

@AliImran 您不知道数据的范围,或者即使它具有对其进行排序的字符串表示形式。 了解更多信息:-en.wikipedia.org/wiki/Sorting_algorithm 【参考方案1】:

我不知道你(或面试官)指的是哪种外部类型,但是

我的建议是 10 路(在您的情况下)合并:

将文件分成 MAX_MEM 大小的块(100 个元素) 这是O(1) 对内存中的每个块进行排序并存储为单独的文件 这是O((n/max_mem) * (max_mem) log(max_mem))) = O(n log(max_mem)) 将所有块作为元素流打开 通过在每个步骤中选择最低元素来合并所有流。 这是 O(n log(n/max_mem)) 使用 minHeap 或 O(n^2/max_mem) 琐碎(在实践中可能更快) 删除区块

关于计算,这是O(n (log(max_mem)+log(n/max_mem)))=O(n log(n))

关于磁盘 I/O,如果一次完成所有合并,则2*n 读取和2*n 写入。 更一般地说,它是(1+[depth of the merge tree])*n

所有写入都是顺序的。 第一次读取是顺序读取,第二次读取是顺序读取,从 10 个文件交错。

如果有更多数据,则需要重复或递归合并(每个块 100 个,然后重复选择 N 个块)。此时,正如@amit 的回答中所述,将拆分+排序步骤替换为替换/选择是值得的,尤其是当数据已经几乎排序时(您可能会完全避开合并步骤)。

请注意,较高的 N 可能会增加计算量(非常轻微,如果您使用正确的结构),但会显着减少磁盘 I/O 量(最多达到一定量;如果一次合并太多块,您可能读取缓冲区的内存不足,导致不必要的读取)。磁盘 I/O 很昂贵,而 CPU 周期则不然。

【讨论】:

正如问题中所述,我已经回答了这个问题,这需要 n*n(logn) 时间,对于面试官来说这是非常高的时间 @AnshuKandhari 不会花这么长时间。为什么要这样做? 第二个参数有一半是错误的。确实,基数排序需要更多内存,但所需的内存取决于您在每次传递中使用的位数(桶数)。因此,所需的内存可能会小于合并排序的要求,例如 from here:-***.com/questions/3539265/… @Jan:你会首先获取 10 个 100 个元素的块并对它们进行排序。 Tim 复杂度= 10*100(log 100) @AnshuKandhari 这个 O(n log(max_mem)),而不是O(n^2 log(max_mem))。 (从技术上讲,第二个语句阅读 theta 而不是 big-oh)。【参考方案2】:

也许面试官希望你问:这些号码是 J. Bentley (Cracking the Oyster) 提到的唯一七位电话号码吗?

【讨论】:

【参考方案3】:

标准的做法是External Sort。

在外部排序中 - 不仅具有O(nlogn) 复杂性很重要 - 尽可能减少磁盘读取/写入,并使最多的读取和写入顺序(而不是随机)也很重要 - 因为按顺序进行磁盘访问效率更高。

这样做的标准方法确实是 k-way 合并排序,正如@JanDvorak 所建议的那样,但是我打算纠正的建议有一些错误和补充:

    首先,对输入执行RS (Replacement-Selection) 会减少初始“运行”次数(递增序列的数量),因此通常会减少后期合并排序所需的迭代总数。 我们需要内存来缓冲(读取和写入输入) - 因此,对于内存大小 M 和文件大小 M*10,我们不能进行 10 路合并 - 这将导致大量读取磁盘(读取每个元素,而不是块)。 k 的标准公式 - 合并的“顺序”是 M/(2b)(其中 M 是您的内存大小,b 是每个“缓冲区”(通常是磁盘块)的大小。 每个合并排序步骤都是通过从先前迭代中生成的每个“运行”中读取 b 条目来完成的 - 将 M/2 填充到内存中。其余内存用于“预测”(允许以最少的 IO 等待持续工作)——从一次运行中请求更多元素,并用于输出缓冲区——以保证块中的顺序正确。 使用这种方法的总迭代次数为log_k(N/(2M)),其中k 是运行次数(之前计算的),M 是内存大小,N 是文件大小。每次迭代都需要对整个文件进行 1 次顺序读取和 1 次顺序写入。

也就是说,file_size/memory_size 的比率通常远大于 10。如果您只对 10 的比率感兴趣,则可能会进行局部优化,但对于 file_size/memory_size >> 10 的更常见情况则不适用

【讨论】:

以上是关于面试难题:用有限的内存对一百万个数字输入进行排序的主要内容,如果未能解决你的问题,请参考以下文章

一百万个结构数组,根据其中一项值排序,用双链表还是数组排序效率更好

一百万个结构数组,根据其中一项值排序,用双链表还是数组排序效率更好,请给出最快C或C++算法代码。

一百万个数求前一百个

如何在100万个整数中 选出最大的10个 java

树状数组。 数组修改某个元素的数值/求出前n个元素的和,需要在一百毫秒处理上百万个数字

使用 mongodb / mongoose 有条件地将 5-20k 文档的输入批次处理成一个包含多达一百万个文档的集合的有效方法是啥?