C++ 面试难题:用有限的内存对一百万个输入进行排序
我试着用外部排序来回答这个问题,但面试官回答说复杂度太高了。 还有更好的选择吗 简化问题:C++ 面试难题:用有限的内存对一百万个输入进行排序,c++,c,algorithm,sorting,data-structures,C++,C,Algorithm,Sorting,Data Structures,我试着用外部排序来回答这个问题,但面试官回答说复杂度太高了。 还有更好的选择吗 简化问题: 假设我们有1000个元素要排序,空间只分配给100个元素。什么是比外部排序花费更少时间的最佳算法。我不知道你(或面试官)指的是哪种外部排序,但是 我的建议是10种方式(在您的情况下)合并: 将文件拆分为最大内存大小的块(100个元素) 这是O(1) 对内存中的每个块进行排序,并作为单独的文件存储 这是O((n/max\u mem)*(max\u mem)log(max\u mem))=O(n lo
假设我们有1000个元素要排序,空间只分配给100个元素。什么是比外部排序花费更少时间的最佳算法。我不知道你(或面试官)指的是哪种外部排序,但是 我的建议是10种方式(在您的情况下)合并:
- 将文件拆分为最大内存大小的块(100个元素)
- 这是
O(1)
- 这是
- 对内存中的每个块进行排序,并作为单独的文件存储
- 这是
=O((n/max\u mem)*(max\u mem)log(max\u mem))
O(n log(max\u mem))
- 这是
- 将所有块作为元素流打开
- 通过在每个步骤中选择最低的元素来合并所有流。
- 这是
使用最小堆或O(n log(n/max\u mem))
非常简单(实际上可能更快)O(n^2/max\u mem)
- 这是
- 删除块
O(n(log(max_mem)+log(n/max_mem))
=O(nlog(n))
关于磁盘I/O,如果所有合并都是在一次过程中完成的,那么这只是2*n
读取和2*n
写入。
更一般地说,它是(1+[合并树的深度])*n
所有写入都是连续的。
第一次读取是顺序的,第二次读取是顺序的,从10个文件交叉读取
如果有更多的数据,则需要重复或递归合并(每个块100个,然后重复选取N个块)。在这一点上,用@amit的答案中描述的替换/选择替换拆分+排序步骤是值得的,特别是当数据已经几乎排序时(您可能完全回避合并步骤)
请注意,较高的N可能会增加计算量(如果使用正确的结构,可能会略微增加计算量),但会显著减少磁盘I/O量(达到一定量;如果一次合并太多块,则可能会耗尽读取缓冲区的内存,导致不必要的读取)。磁盘I/O很贵,CPU周期也不贵。我不知道你(或面试官)指的是哪种外部类型,但是 我的建议是10种方式(在您的情况下)合并:
- 将文件拆分为最大内存大小的块(100个元素)
- 这是
O(1)
- 这是
- 对内存中的每个块进行排序,并作为单独的文件存储
- 这是
=O((n/max\u mem)*(max\u mem)log(max\u mem))
O(n log(max\u mem))
- 这是
- 将所有块作为元素流打开
- 通过在每个步骤中选择最低的元素来合并所有流。
- 这是
使用最小堆或O(n log(n/max\u mem))
非常简单(实际上可能更快)O(n^2/max\u mem)
- 这是
- 删除块
O(n(log(max_mem)+log(n/max_mem))
=O(nlog(n))
关于磁盘I/O,如果所有合并都是在一次过程中完成的,那么这只是2*n
读取和2*n
写入。
更一般地说,它是(1+[合并树的深度])*n
所有写入都是连续的。
第一次读取是顺序的,第二次读取是顺序的,从10个文件交叉读取
如果有更多的数据,则需要重复或递归合并(每个块100个,然后重复选取N个块)。在这一点上,用@amit的答案中描述的替换/选择替换拆分+排序步骤是值得的,特别是当数据已经几乎排序时(您可能完全回避合并步骤)
请注意,较高的N可能会增加计算量(如果使用正确的结构,可能会略微增加计算量),但会显著减少磁盘I/O量(达到一定量;如果一次合并太多块,则可能会耗尽读取缓冲区的内存,导致不必要的读取)。磁盘I/O成本很高,CPU周期也不高。标准的做法是使用磁盘 在外部排序中,不仅要有
O(nlogn)
comlexity,而且要尽可能减少磁盘读/写,并使大多数读/写按顺序进行(而不是随机进行),因为按顺序进行磁盘访问效率更高
正如@JanDvorak所建议的那样,这样做的标准方法确实是k路合并排序,但我要纠正的建议中存在一些错误和补充:
k
的标准公式-合并的“顺序”是M/(2b)
(其中M是内存大小,b
是每个“缓冲区”(通常是磁盘块)的大小b
条目来完成的-将M/2
填充到内存中。内存的其余部分用于“预测”(这允许以最少的IO等待进行连续工作)-从运行中请求更多元素,并为输出缓冲区请求更多元素-以保证块中的顺序正确log_k(N/(2M))
,其中k
是运行次数(先前计算),M
是内存大小,N
是文件大小。每次迭代需要对整个文件进行1次顺序读取和1次顺序写入也就是说,文件大小/内存大小的比率是u