web-dev-qa-db-ja.com

外部マージソートアルゴリズムの仕組み

外部マージソートアルゴリズムがどのように機能するかを理解しようとしています(同じ質問に対する回答を見ましたが、必要なものが見つかりませんでした)。 Jeffrey McConnellの「Analysis Of Algorithms」という本を読んでいます。そこで説明されているアルゴリズムを実装しようとしています。

たとえば、入力データがあります:3,5,1,2,4,6,9,8,7、および私は4つの数字のみをメモリにロードできます。

私の最初のステップは、入力ファイルを4つの番号のチャンクで読み取り、メモリ内でソートし、ファイルAとファイルBの隣に書き込みます。

私が得た:

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

今、私の質問は、メモリに収まらない場合、これらのファイルのチャンクをより大きなものにマージするにはどうすればよいですか? Jeffrey McConnellは、半分のチャンクを読み取り、それらを次のファイルCおよびDにマージする必要があると書いています。

しかし、間違ったシーケンスを取得しました:

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

誰でもステップバイステップの指示で例を提供できますか?

PS:ファイルから読み取ることで番号ごとにマージする方法を理解していますが、I/O操作を減らすためにメモリ内バッファでそれをどのように行うのですか?

35
KolKir

長い時間を経て、あなたは答えを得たに違いないと思います。しかし、私はまだこの質問に答える誰かを助けるためにいくつかのリンク例を提供しています。

注:このリンクを調べる前に、Heapデータ構造について理解しておく必要があります双方向ソートの例 および 多方向外部ソートの例そして、外部ソートアルゴリズムの実装の完全なアイデアが得られます。

40
anuj pradhan

まず、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]

これで、AのRAMにあった部分がマージを終了したため、その後半をメモリに入れる必要があります。メモリには{[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個ある場合、4つの数字からなる5つのチャンクを作成し、そのたびに2つのチャンクを結合およびマージして、8つの数字からなる2つのチャンク(および4つの数字の追加)を作成します。次に、新しいチャンクを16個の数字のいずれかにマージします。

30
Savvas

同時にファイルを反復処理します。

各ファイルの先頭から始めて、他のファイルより大きくない(つまり小さいまたは等しい)要素を選択し続け、その要素を新しいファイルに出力してイテレータを増やします。

あなたの最後の声明から、あなたがすでにこれを行うことを知っているかどうかは不明ですが、これがあなたがする必要があるのはこれだけです:

  • ファイルごとにメモリに1つの番号があれば十分です。もちろん、この演習の目的のためにおそらく無視されるインデックスやその他の変数も必要です。

  • このプロセス中にファイルを正しい位置で開いたままにすることができるため、各ファイルを1回読み取るだけで済み、正しい位置に到達するためにファイル全体を再度読み取る必要はありません。

だから、のために:

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

まず、各ファイルの最初の要素(1および4)から始めます。

1は小さいため、新しいファイルに出力して2に進みます。

24よりも小さいため、それを出力して3に進みます。

34よりも小さいため、それを出力して5に進みます。

45よりも小さいため、それを出力して6に進みます。

56よりも小さいため、それを出力すると、Aの終わりに到達します。

ここで、残りのBを出力します:6, 8, 9

これにより、[1,2,3,4,5,6,8,9]が得られます。

5
Dukeling

通常、外部ソートは、大きすぎてメモリに収まらないファイルをソートする必要がある場合に使用されます。

トリックは、大きい入力ファイルをk個のソートされた小さいチャンクに分割し、チャンクを大きいソート済みファイルにマージすることです。マージには、最小ヒープを使用します。 kはメモリのしきい値に依存します。

各チャンクから(メモリのしきい値に応じて)特定の数のレコードを読み取り、チャンクごとにキューに入れます。

各キューから左端のアイテム(キュー内のアイテムが並べ替えられるため、これが最小のアイテムになります)をポップし、ヒープにプッシュします

ヒープから最小アイテムをポップします。どのキューから来たかに注意してください

キューにない対応するチャンクの次のアイテムをキューに補充します

キューから左端のアイテムをポップし、ヒープにプッシュします

出力ファイルに最小アイテムを書き込みます

ヒープが空になるまで上記の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 __== '__main__':
    external_sort(sys.argv[1], sys.argv[2], sys.argv[3])
4
lalatnayak

[〜#〜] readme [〜#〜]ファイルを読んで、外部マージソートを適切に理解してください。

定義された段階的な実装があります

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

3
melvil james