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

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

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

【问题讨论】:

标签: c++ c algorithm sorting data-structures


【解决方案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:-stackoverflow.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 所建议的那样,但是我打算纠正的建议有一些错误和补充:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多