我在理解外部排序算法中的合并步骤时遇到了一定的麻烦。我在维基百科中看到了这个例子但是无法理解它。
外部排序的一个例子是外部合并排序算法,它对每个适合RAM的块进行排序,然后将排序后的块合并在一起。例如,仅使用100兆字节的RAM对900兆字节的数据进行排序: 1)读取主存储器中的100 MB数据,并按照常规方法排序,如quicksort。 2)将已排序的数据写入磁盘。 3)重复步骤1和2,直到所有数据都处于排序的100 MB块(有900MB / 100MB = 9块),现在需要合并为一个单独的输出文件。 4)将每个已排序块的前10 MB(= 100MB /(9个块+ 1))读入主存储器中的输入缓冲区,并将剩余的10 MB分配给输出缓冲区。 (实际上,它可以提供更好的性能,使输出缓冲区更大,输入缓冲区略小。) 5)执行9路合并并将结果存储在输出缓冲区中。如果输出缓冲区已满,请将其写入最终的已排序文件,并将其清空。如果9个输入缓冲区中的任何一个变为空,请使用其关联的100 MB排序块的下一个10 MB填充它,直到该块中没有更多数据可用。
我无法理解第4步。当我们有100 MB的可用内存时,为什么要读取前10 MB的内存。我们如何确定外部合并中的通过次数?我们会对每个块进行排序并将它们存储在9个文件?
答案 0 :(得分:1)
假设您已将要排序的范围分解为k个已排序的元素块。如果您可以执行这些已排序块的k-way合并并将结果写回磁盘,那么您将对输入进行排序。
要进行k-way合并,需要存储k个读指针,每个文件一个,并重复查看所有k个元素,取最小值,然后将该元素写入输出流并前进相应的读指针。
现在,由于您将所有数据存储在磁盘上的文件中,因此您无法将指针存储到尚未读取的元素中,因为您无法将所有内容都放入主内存中。
因此,让我们从一种简单的方法开始,模拟普通合并算法的作用。假设您在内存中存储k个元素的数组。您从每个文件中读取一个元素到每个数组槽。然后,重复以下步骤:
这种方法可以正常工作,但速度会很慢。请记住,磁盘I / O操作比主内存中的相应操作需要更多,多。这种合并算法最终做Θ(n)磁盘读取(我假设k远小于n),因为每次选择下一个元素时,我们都需要进行另一次读取。这将是非常昂贵的,所以我们需要一个更好的方法。
让我们考虑一下修改。现在,我们不存储每个文件一个k元素的数组,而是存储一个k个插槽的数组,每个插槽都保存相应文件中的前R个元素。为了找到要输出的下一个元素,我们扫描整个数组,并为每个数组查看我们尚未考虑的第一个元素。我们取最小值,将其写入输出,然后从数组中删除该元素。如果这清空了数组中的一个插槽,我们通过从文件中读取更多元素来补充它。
这更复杂,但它显着减少了我们需要执行的磁盘读取次数。具体来说,由于元素是以大小为R的块读取的,我们只需要进行Θ(n / R)磁盘读取。
我们可以采取类似的方法来最小化写入。我们不是一次一个地将每个元素写入磁盘(需要Θ(n)写入),而是存储大小为W的缓冲区,在我们去的时候将元素累积到它中,并且只有在填充后才写入缓冲区。这需要Θ(n / W)磁盘写入。
显然,让R和W更大将使这种方法更快,但代价是更多的内存。具体来说,我们需要kR项的空间来存储大小为R的读缓冲区的k个副本,并且我们需要W项的空间来存储大小为W的写缓冲区。因此,我们需要选择R和W以便kR + W物品适合主记忆。在上面给出的示例中,您有100MB的主内存和900MB的排序。如果将数组拆分为9个,则需要选择R和W,以便(kR + W)·sizeof(record)
≤100MB。如果每个项目都是一个字节,则选择R = 10MB且W = 10MB可确保一切都适合。这也可能是一个相当不错的发行版,因为它可以保持较低的读写次数。