Как работает внешний алгоритм сортировки слиянием?
Я пытаюсь понять, как работает алгоритм сортировки внешнего слияния (я видел некоторые ответы на тот же вопрос, но не нашел то, что мне нужно). Я читаю книгу" анализ алгоритмов " Джеффри Макконнелла, и я пытаюсь реализовать алгоритм, описанный там.
например, у меня есть входные данные: 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])