外部合并排序算法如何工作?

时间:2013-12-27 14:29:47

标签: algorithm sorting mergesort external-sorting

我正在尝试理解外部合并排序算法是如何工作的(我看到了相同问题的一些答案,但没有找到我需要的东西)。我正在阅读Jeffrey McConnell撰写的“分析算法”一书,我正在尝试实现那里描述的算法。

例如,我输入了数据:3,5,1,2,4,6,9,8,7,我只能将4个数字加载到内存中。

我的第一步是读取4个数字块的输入文件,在内存中对它们进行排序,然后将一个写入文件A,然后写入文件B.

我得到了:

A:[1,2,3,5][7]  
B:[4,6,8,9]

现在我的问题是如何将这些文件中的块合并到较大的块中,如果它们不适合内存?杰弗里麦康奈尔写道,我需要阅读半块并将它们合并到下一个文件C和D.

但我的序列错了:

C:[1,2,4,6,3,8,5,9]
D:[7]

有人可以提供分步说明的例子吗?

PS:我理解如何通过读取文件来合并数字,但是如何使用内存缓冲区来减少I / O操作呢?

5 个答案:

答案 0 :(得分:35)

我想经过这么长时间你一定得到了答案。但我仍然提供一些示例链接来帮助其他人来解决这个问题。

注意:在查看此链接之前,您应该了解数据结构 看看 Example of Two-Way SortingExample of multiway external sorting ,您将完全了解外部排序算法的实现

答案 1 :(得分:29)

首先,通过对4个数字的部分数字进行排序,你应该得到3个数据块。

A:[1,2,3,5]  
B:[4,6,8,9]
C:[7]

然后你将读取每个文件的一半(忽略C,因为它不适合)并合并它们。所以,你将加载到内存{[1,2],[4,6]}。您将进行偶然合并,并将结果写入新的块D:

Compare 1 and 4 -> D:[1]
Compare 2 and 4 -> D:[1, 2]

现在,在RAM中的A部分已完成合并,所以现在你必须把它的后半部分带到内存中。现在你的记忆将有{[3,5],[4,6]}。

Compare 3 and 4 -> D:[1, 2, 3]
Compare 5 and 4 -> D:[1, 2, 3, 4]
Compare 5 and 6 -> D:[1, 2, 3, 4, 5]

所有的块A都被合并了,所以现在只需将B的其余部分附加到D

D:[1,2,3,4,5,6,8,9]

现在你必须使用块C和D执行相同的过程。请记住,在另一个示例中,C可以有多个数字。通过mergind C和D,你将获得一个新的块E,它将成为最终的排序文件。

另外,请注意,在一个更大的示例中,您可能需要更多合并阶段。例如,如果您有20个数字要排序,您将创建5个4个数字的块,然后您将每次合并并合并其中两个,产生2个8个数字的块(加上4个数字的一​​个额外),以及然后将较新的块合并为16个数字中的一个,依此类推。

答案 2 :(得分:5)

您将同时遍历这些文件。

从每个文件的开头开始,继续选择哪个文件的元素不大于另一个文件(即小于或等于),将该元素输出到新文件并增加迭代器。

从你上次发表的声明来看,目前还不清楚你是否已经知道这样做了,但这就是你需要做的全部,因为:

  • 您只需要在每个文件的内存中都有一个数字,当然还有任何索引和其他变量可能会因此练习而被忽略。

  • 您只需要读取一次文件,因为在此过程中您可以将文件保持在正确的位置,这样您就不需要再次读取整个文件以找到正确的位置。

所以,对于:

A:[1,2,3,5]
B:[4,6,8,9]

您将从每个文件中的第一个元素开始 - 14

1较小,因此您将其输出到新文件并转到2

2小于4,因此您输出该内容并转到3

3小于4,因此您输出该内容并转到5

4小于5,因此您输出该内容并转到6

5小于6,因此您输出该值,然后您已达到A的结尾。

现在只输出B的其余部分:6, 8, 9

这会为您提供[1,2,3,4,5,6,8,9]

答案 3 :(得分:3)

External sorting is usually used when you need to sort files that are too large to fit into memory.

The trick is to break the larger input file into k sorted smaller chunks and then merge the chunks into a larger sorted file. For the merge use a min heap. k will depend on your memory threshold.

Read a certain number of records (depending on your memory threshold) from each chunk and put it in a queue per chunk.

Pop the leftmost item (This will be the smallest item as the items in the queue will be sorted) from each queue and push it to the heap

Pop the min item from the heap. Note what queue it came from

Replenish the queue with the next item from it's corresponding chunk that is not in the queue

Pop the left most item from the queue and push it to the heap

Write the min item to the output file

Continue the above 4 steps till the heap is empty

Sample python code (Does not merge in place)

import os
import heapq
import itertools
import linecache
from collections import deque
import sys


def external_sort(input_directory, input_file_name, output_file_name):
    with open(os.path.expanduser(input_directory + '/' + output_file_name), 'w+') as f:
        heap = []
        pages = {}
        next_line_numbers = {}
        has_more_items = {}
        chunk_file_paths, max_chunk_size = create_sorted_chunks(input_directory, input_file_name)
        max_page_size = max_chunk_size // 10
        for chunk_file_path in chunk_file_paths:
            pages[chunk_file_path] = populate_page(chunk_file_path, max_page_size)
            next_line_numbers[chunk_file_path] = len(pages[chunk_file_path])
            has_more_items[chunk_file_path] = True
        for chunk_file_path in chunk_file_paths:
            heapq.heappush(heap, pages[chunk_file_path].popleft())
        while heap:
            item, chunk_file_path = heapq.heappop(heap)
            f.write(str(item)+'\n')
            if has_more_items[chunk_file_path]:
                has_more_items[chunk_file_path] = append_next(pages, chunk_file_path, next_line_numbers[chunk_file_path])
                next_line_numbers[chunk_file_path] += 1
            if pages[chunk_file_path]:
                heapq.heappush(heap, pages[chunk_file_path].popleft())
    for chunk_file_path in chunk_file_paths:
        os.remove(chunk_file_path)


def populate_page(chunk_file_path, max_page_size):
    chunk = deque()
    with open(chunk_file_path, 'r') as f:
        for line in itertools.islice(f, 0, max_page_size):
            chunk.append((int(line), chunk_file_path))
    return chunk


def append_next(chunks, chunk_file_path, line_number):
    chunk = chunks[chunk_file_path]
    item = linecache.getline(chunk_file_path, line_number)
    if item and len(item) > 0:
        chunk.append((int(item), chunk_file_path))
        has_more = True
    else:
        has_more = False
    return has_more


def create_sorted_chunks(input_file_directory, input_file_name):
    input_file_path = os.path.expanduser(input_file_directory + '/' + input_file_name)
    suffix = 1
    begin, end, tot = 0, 0, 0
    chunk_file_paths = []
    with open(input_file_path, 'r') as f:
        for line in f.readlines():
            tot += 1
    end = tot//10
    while suffix <= 10:
        buffer = []
        chunk_file_name = 'temp' + str(suffix) + '.txt'
        chunk_file_path = os.path.expanduser(input_file_directory + '/' + chunk_file_name)
        if not os.path.isfile(chunk_file_path):
            with open(os.path.expanduser(input_file_path), 'r') as f:
                for line in itertools.islice(f, begin, end):
                    buffer.append(int(line))
                create_chunk(chunk_file_path, buffer)
        suffix += 1
        begin = end
        end += tot//10
        chunk_file_paths.append(chunk_file_path)
    return chunk_file_paths, tot//10


def create_chunk(chunk_file_path, buffer):
    buffer.sort()
    with open(chunk_file_path, 'w+') as f:
        for i in buffer:
            f.write(str(i) + '\n')


if __name__ == '__main__':
    external_sort(sys.argv[1], sys.argv[2], sys.argv[3])

答案 4 :(得分:2)

请阅读自述文件文件以正确了解外部合并排序。

  

已定义了分步实施

https://github.com/melvilgit/external-Merge-Sort/blob/master/README.md