Как работает внешний алгоритм сортировки слиянием?

Я пытаюсь понять, как работает алгоритм сортировки внешнего слияния (я видел некоторые ответы на тот же вопрос, но не нашел то, что мне нужно). Я читаю книгу" анализ алгоритмов " Джеффри Макконнелла, и я пытаюсь реализовать алгоритм, описанный там.

например, у меня есть входные данные: 3,5,1,2,4,6,9,8,7 и я могу загрузить только 4 цифры в памяти.

мой первый шаг-прочитать входной файл в 4-номерных кусках, отсортировать их в памяти и записать в файл A и рядом с файлом Б.

Я:

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: Я понимаю, как объединить номер по номеру, прочитав из файла, но как это сделать с буферами в памяти, чтобы уменьшить операции ввода-вывода?

4 ответов


Я думаю, после такого долгого времени вы, должно быть, получили ответ. Но я все еще предоставляю некоторые примеры ссылок, чтобы помочь кому-то еще, кто попадает в этот вопрос.

Примечание: прежде чем смотреть на эту ссылку, вы должны иметь представление о кучу структуры данных Взгляните на пример двусторонней сортировки и пример многоходовой внешней сортировки и вы получите полное представление о реализации внешней сортировки алгоритм


прежде всего, сортируя числа по частям 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]

теперь часть, которая была в ОЗУ, закончила слияние, поэтому теперь вам придется принести вторую половину ее в память. Теперь ваша память будет {[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 чисел и так далее.


вы будете перебирать файлы одновременно.

просто начните с начала каждого файла и продолжайте выбирать, какой элемент файла не больше (т. е. меньше или равен), чем другой, выведите этот элемент в новый файл и увеличьте итератор.

из вашего последнего заявления неясно, знаете ли вы это или нет, но это все, что вам нужно сделать, потому что:

  • вам нужно только иметь один номер в памяти для каждого из файлов и, конечно, любые индексы и другие переменные, которые предположительно игнорируются для целей этого упражнения.

  • вам нужно только прочитать каждый файл один раз, так как вы можете держать файлы открытыми в правильном положении во время этого процесса, поэтому вам не нужно снова читать весь файл, чтобы добраться до правильного положения.

так:

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

вы бы начали с первого элемента из каждый файл - 1 и 4.

на 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].


внешняя сортировка обычно используется, когда вам нужно сортировать файлы, которые слишком велики, чтобы поместиться в память.

трюк состоит в том, чтобы разбить больший входной файл на K отсортированных меньших кусков, а затем объединить куски в больший отсортированный файл. Для слияния используйте минимальную кучу. k будет зависеть от порога памяти.

прочитайте определенное количество записей (в зависимости от порога памяти) из каждого фрагмента и поместите его в очередь на фрагмент.

Pop самый левый элемент (Это будет самый маленький элемент, поскольку элементы в очереди будут отсортированы) из каждой очереди и нажмите его в кучу

Pop элемент min из кучи. Обратите внимание, из какой очереди он пришел

пополните очередь следующим элементом из соответствующего куска, который не находится в очереди

Pop самый левый элемент из очереди и нажмите его в кучу

запишите элемент min в выходной файл

продолжить вышеуказанные 4 шага, пока куча пусто

пример кода python (не сливается на месте)

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])