web-dev-qa-db-ja.com

リンクリストをソートするためにクイックソートよりもマージソートが優先される理由

私はフォーラムで次を読みました:

結合ソートは、リンクリストのような不変のデータ構造に対して非常に効率的です。

そして

通常、クイックソートは、データがメモリに格納されている場合、マージソートよりも高速です。ただし、データセットが巨大で、ハードドライブなどの外部デバイスに格納されている場合、マージソートが速度の面で明確な勝者です。外付けドライブの高価な読み取りを最小限に抑えます

そして

リンクされたリストを操作する場合、マージソートに必要な補助ストレージはわずかで一定です

誰かが上記の議論を理解するのを助けることができますか?巨大なリンクリストをソートするのに、なぜマージソートが好ましいのですか?また、外部ドライブへの高価な読み取りを最小限に抑えるにはどうすればよいですか?基本的に、大きなリンクリストをソートするためにマージソートを選択する理由を理解したいと思います。

60
maxpayne

クイックソートは、インプレースソートに適しています。特に、ほとんどの操作は、配列内の要素のペアの交換に関して定義できます。ただし、これを行うには、通常、2つのポインター(またはインデックスなど)を使用して配列を「ウォーク」します。1つは配列の先頭から始まり、もう1つは末尾から始まります。両方が中央に向かって動作します(そして、それらが出会うと、特定のパーティションステップが完了します)。ファイルは、最初から最後まで一方向の読み取りに主に向けられているため、ファイルでは高価です。最後から始めて後方にシークするのは、通常比較的高価です。

少なくともその最も単純な化身では、マージソートはほとんど逆です。それを実装する簡単な方法は、データを一方向に調べることだけを必要とします。butは、データを2つの別々の断片に分割し、断片をソートしてから、それらを結合し直します。

リンクリストを使用すると、1つのリンクリスト内の要素を交互に(たとえば)簡単に取得し、リンクを操作して、代わりに同じ要素から2つのリンクリストを作成できます。配列を使用すると、元のデータと同じ大きさのコピーを作成する場合は要素を並べ替えて別の配列に配置するのは簡単ですが、それ以外の場合はそれほど重要ではありません。

同様に、ソース配列の要素をデータのある新しい配列に順番にマージする場合、配列のマージは簡単ですが、データのまったく新しいコピーを作成せずに適切な場所で行うことはまったく別の話です。リンクリストを使用すると、2つのソースリストの要素を1つのターゲットリストにマージするのは簡単です。ここでも、要素をコピーせずにリンクを操作するだけです。

Quicksortを使用して外部マージソートのソートされた実行を生成することに関しては、動作しますが、(決定的に)準最適です。マージソートを最適化するには、通常、ソートした各「実行」の長さを最大化する必要があります。メモリに収まるデータを単純に読み込み、クイックソートして書き出すと、各実行は使用可能なメモリのサイズに制限されます(少し小さくなります)。

ただし、原則としてそれよりもかなり良いことができます。データのブロックを読み込むことから始めますが、Quicksortを使用する代わりに、ヒープを構築します。次に、各アイテムをヒープからソートされた「実行」ファイルに書き込むときに、入力ファイルからanother item inを読み取ります。ディスクに書き込んだアイテムよりも大きい場合は、既存のヒープに挿入して繰り返します。

小さいアイテム(つまり、既に書き込まれているアイテムの前に属するアイテム)は、別々に保持し、2番目のヒープに構築します。最初のヒープが空で、2番目のヒープがすべてのメモリを引き継いだ場合(のみ)、既存の「実行」ファイルへのアイテムの書き込みを終了し、新しいファイルで開始します。

これがどれほど効果的であるかは、データの最初の順序に依存します。最悪の場合(入力は逆順にソートされます)、まったく役に立ちません。最良の場合(入力は既にソート済み)、入力を1回実行するだけでデータを「ソート」できます。平均的なケース(ランダムな順序で入力)で、ソートされた各実行の長さを約2倍にできます。これにより、通常、速度がaround 20-25%向上します(ただし、割合は、データは利用可能なメモリを超えています)。

48
Jerry Coffin

クイックソートは、配列または類似の構造にインデックスを付けることができることに依存しています。それが可能な場合、Quicksortに勝るものはありません。

ただし、リンクされたリストにすぐに直接インデックスを作成することはできません。つまり、myListがリンクリストの場合、myList[x]は、そのような構文を記述できた場合、リストの先頭から開始し、最初のxリンクをたどることになります。これは、Quicksortが行うすべての比較に対して2回実行する必要があり、それは実際に高価になります。

ディスク上の同じもの:Quicksortは、比較したいすべてのアイテムを探して読む必要があります。

このような状況では、アイテムを順番に読み取り、通常はlog2(N)がデータを渡すため、マージソートは高速です。関係するI/Oがはるかに少なくなり、リンクリスト内のリンクをたどるのに費やす時間が大幅に短縮されます。

クイックソートは、データがメモリに収まると高速で、直接アドレス指定できます。 Mergesortは、データがメモリに収まらない場合、またはアイテムに到達するのに費用がかかる場合に高速です。

通常、大きなファイルの並べ替えは、ファイルの可能な限り多くをメモリにロードし、それをクイックソートして一時ファイルに書き出し、ファイル全体を処理するまで繰り返すことに注意してください。その時点で、いくつかのブロックがあり、それぞれがソートされ、プログラムはN方向のマージを実行してソートされた出力を生成します。

20
Jim Mischel

クイックソートは、レコードをリストの中央に移動します。アイテムをインデックスXに移動するには、0から開始し、一度に1つのレコードを反復する必要があります。

マージソートは、リストをいくつかの小さなリストに分割し、リストの先頭の項目のみを比較します。

通常、マージソートのセットアップは、クイックソートで必要な反復よりもコストが高くなります。ただし、リストが十分に大きい場合、または読み取りが高価な場合(ディスクからの場合など)、クイックソートの反復に要する時間が主要な要因になります。

3
cadrell0