对几乎已排序的大文件进行排序
Posted
技术标签:
【中文标题】对几乎已排序的大文件进行排序【英文标题】:sorting huge file that is almost sorted 【发布时间】:2015-03-26 20:23:29 【问题描述】:我面临以下问题:
我有一个巨大的文件(比如 30 GB),它使用特定的 API 在内存中流式传输。
此 API 仅允许我向前(而不是向后)阅读。但是这些文件可以读取任意多次。
该文件包含几乎所有已排序的数据,例如,99% 的数据已排序,但可能会发生记录不在其正确位置,并且如果所有内容都已排序,则应该在很早之前插入.
我正在尝试创建此文件的副本,但需要对其进行排序。
有没有优雅的方法来做到这一点?
我能想到的唯一方法是最通用的方法:
读取文件 创建一批几 GB 的内存,对它们进行排序,将它们写入 HDD 上的文件 使用外部合并将所有这些临时文件合并到最终输出中但是,这并没有使用数据“几乎”排序的特性。会有更好的方法来做到这一点吗?例如不使用硬盘上的外部文件?
【问题讨论】:
似乎微不足道 - 将元素放在 300 MB 优先级队列中,然后合并。问题在哪里? 搜索(外部)归并排序。它将数据切割成可管理的块,单独对每个块进行排序,然后将所有块合并在一起。 这是我在问题末尾提出的建议,但是我想知道是否有一种方法可以更好地了解我的数据结构是特定的 您会发现,使用替换选择分发传递时,您最终可能只会合并两个文件,甚至可能只有一个,如果输入已经几乎排序的话。 如果所有乱序元素都出现在它们应该出现的位置之后,您可以直接遍历文件,将其拆分为已排序和未排序的部分(未排序的元素只是那些比最近看到的已排序子集的元素小),对未排序的部分进行排序(考虑到它非常小,这应该不是问题),然后将两者合并。 【参考方案1】:您可以这样做(Python 中的示例)
last = None
special = []
for r in records:
if last is None or r > last:
last = r
else:
special.append(r)
if len(special) > max_memory:
break
if len(special) > max_memory:
# too many out of sequence records, use a regular sort
...
else:
sort(special)
i = 0
for r in records:
while i < len(special) and special[i] < r:
write(special[i])
i += 1
write(r)
while i < len(special):
write(special[i])
i += 1
【讨论】:
如果还有大量记录非常接近它们应该在的位置,那么在第一遍添加一个小的循环排序缓冲区可能会有所帮助。这个缓冲区可以通过简单的插入排序保持有序,并且可以显着减少最终在special
数组中的记录数。【参考方案2】:
使用一种称为自然归并排序的自下而上归并排序的变体。这里的想法是找到有序数据的运行,然后在两个文件(所有顺序 I/O)之间反复合并这些运行,直到只剩下一个运行。如果排序不必是稳定的(保持相等元素的顺序),那么您可以考虑在一对连续元素无序时出现运行边界。这消除了一些家务。如果排序需要稳定,那么您需要在找到运行的初始通道上跟踪运行边界,这可能是一个计数数组(每次运行的大小)。希望这个数组适合内存。每次合并后,数组中的计数减少一半,一旦只有一个计数,排序就完成了。
Wiki 文章(虽然没有给出示例代码):natural bottom up merge sort。
如果所有乱序元素都包含一些孤立的记录,您可以将乱序元素分离到第三个文件中,只将第一个文件中的有序记录复制到第二个文件中。然后你用你想要的任何方法对第三个文件进行排序(如果第三个文件很大,自下而上的合并排序可能仍然是最好的),然后合并第二个和第三个文件以创建一个排序文件。
如果您有多个硬盘驱动器,请将文件保存在不同的驱动器上。如果在 SSD 驱动器上执行此操作,则无关紧要。如果使用单个硬盘,一次读取或写入大量记录,例如每次读取或写入 10MB 到 100MB,将大大减少排序过程中的寻道开销。
【讨论】:
以上是关于对几乎已排序的大文件进行排序的主要内容,如果未能解决你的问题,请参考以下文章