我尝试使用外部排序来回答这个问题,但面试官回答说复杂度很高 nn(log(n)) 即 n 平方 *logn。有没有更好的选择。
为简化问题:假设我们有 1000 个元素要排序,仅分配给 100 个元素的空间。什么是比外部排序花费更少时间的最佳算法。
我尝试使用外部排序来回答这个问题,但面试官回答说复杂度很高 nn(log(n)) 即 n 平方 *logn。有没有更好的选择。
为简化问题:假设我们有 1000 个元素要排序,仅分配给 100 个元素的空间。什么是比外部排序花费更少时间的最佳算法。
我不知道你(或面试官)指的是哪种外部类型,但是
我的建议是 10 路(在您的情况下)合并:
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
reads only 和2*n
writes only。更一般地说,它是(1+[depth of the merge tree])*n
所有写入都是顺序的。第一次读取是顺序的,第二次是顺序的,从 10 个文件交错。
如果有更多数据,则需要重复或递归合并(每个块 100 个,然后重复选择 N 个块)。此时,正如@amit 的回答中所述,将拆分+排序步骤替换为替换/选择是值得的,尤其是当数据已经几乎排序时(您可能会完全避开合并步骤)。
请注意,较高的 N 可能会增加计算量(非常轻微,如果您使用正确的结构),但会显着减少磁盘 I/O 的数量(最多达到一定数量;如果一次合并太多块,您可能会用完读取缓冲区的内存,导致不必要的读取)。磁盘 I/O 很昂贵,而 CPU 周期则不然。
也许面试官希望你问:这些号码是 J. Bentley (Cracking the Oyster)提到的唯一七位数电话号码吗?
这样做的标准方法是外部排序。
在外部排序中 - 不仅具有O(nlogn)
复杂性很重要 - 尽可能减少磁盘读取/写入,并使最多的读取和写入顺序(而不是随机)也很重要 - 因为磁盘访问效率更高当按顺序完成时。
这样做的标准方法确实是 k-way 合并排序,正如@JanDvorak 所建议的那样,但是我打算纠正的建议有一些错误和补充:
k
M/(2b)
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