web-dev-qa-db-ja.com

なぜRadix Sortが頻繁に使用されないのですか?

安定しており、時間の複雑さはO(n)です。 QuicksortやMergesortのようなアルゴリズムよりも高速である必要がありますが、使用されていることはほとんどありません。

32
Queequeg

基数ソートとは異なり、クイックソートは汎用的ですが、基数ソートは固定長整数キーにのみ役立ちます。

また、O(f(n))は実際にはK * f(n)の順序を意味することを理解する必要があります。ここで、Kは任意の定数です。基数ソートの場合、このKは非常に大きい(少なくともソートされた整数のビット数の順序)、一方、クイックソートはすべてのソートアルゴリズムの中で最も低いKの1つであり、n * log(n)の平均的な複雑さを持っています。したがって、実際のシナリオではクイックソートは多くの場合、基数ソートよりも高速です。

40
vartec

ほとんどの並べ替えアルゴリズムは汎用です。比較関数が与えられれば、それらは何でも機能し、QuicksortやHeapsortなどのアルゴリズムはO(1)追加のメモリでソートします。

基数ソートはより専門的です。辞書式順序の特定のキーが必要です。キー内の可能なシンボルごとに1つのバケットが必要であり、バケットは多くのレコードを保持する必要があります。 (または、可能なすべてのキー値を保持する1つの大きなバケット配列が必要です。)基数ソートを実行するには、より多くのメモリが必要になる可能性が高く、ランダムに使用します。 Quicksortがキャッシュミスを取得するなどのページフォールトが発生する可能性があるため、これは最近のコンピューターには適していません。

最後に、人々は一般的に自分のソートアルゴリズムをもう書いていません。ほとんどの言語にはソートするためのライブラリー機能があり、正しいことは通常それらを使用することです。基数ソートは普遍的に適用可能ではないため、通常は実際の使用に合わせて調整する必要があり、追加のメモリを大量に使用するため、ライブラリ関数またはテンプレートに配置するのは困難です。

20
David Thornley

並べ替えのキーが実際に既知の疎な範囲の整数であることは非常にまれです。通常、アルファベットのフィールドがあり、それは非比較ソートをサポートするようなlookですが、実際の文字列はアルファベット全体に均等に分散されていないため、これは正常に機能しません理論。

それ以外の場合、基準は定義されますonly運用上(2つのレコードが与えられた場合、どちらが最初に来るかを決定できますが、分離されたレコードがどれだけ「はるかに」小さいかを評価できません)。したがって、この方法は多くの場合、適用できないか、考えられるほど適用性が低いか、またはO(n * log(n))よりも速くありません。

5
Kilian Foth

私はいつもそれを使用しており、実際には比較ベースのソートよりも優れていますが、私は確かに他の何よりも数値を操作する奇妙なボールです(私は文字列をほとんど操作せず、その時点で基数がインターンされている場合は基数)並べ替えは、重複を除外し、セットの共通部分を計算するのに再び役立ちます。私は実際には辞書式比較を行いません)。

基本的な例は、検索または中央分離の一部として指定された次元による基数ソートポイント、または一致するポイント、深度ソートフラグメントを検出する簡単な方法、または複数のループで使用されるインデックスの配列を基数ソートして、よりキャッシュに適したアクセスを提供することです。パターン(メモリ内を行き来して、もう一度戻って同じメモリをキャッシュラインに再ロードするだけではありません)。少なくとも私のドメイン(コンピューターグラフィックス)には、固定サイズの32ビットおよび64ビットの数値キーでソートするための非常に幅広いアプリケーションがあります。

私が提案して言いたかったことの1つは、基数ソートは浮動小数点数と負数に対して機能することですが、FPバージョンをできるだけ移植性のあるものにすることは困難です。また、それはO(n * K)ですが、Kはキーサイズのバイト数でなければなりません(たとえば、バケットに2 ^ 8のエントリがある場合、100万の32ビット整数は通常4バイトサイズのパスを取得します。 )。メモリアクセスパターンは、通常、並列配列と小さなバケット配列(2番目は通常、スタックにぴったりと収まる)が必要な場合でも、クイックソートよりもキャッシュに優しい傾向があります。 QSは、散発的なランダムアクセスパターンで100万の整数の配列を並べ替えるために、5,000万回のスワップを実行する場合があります。基数ソートは、データ上でキャッシュに適した4つの線形パスでこれを行うことができます。

ただし、浮動小数点とともに負の数で小さなKを使用してこれを実行できるという認識の欠如は、基数ソートの人気の欠如に大きく貢献している可能性があります。

人々がそれをより頻繁に使用しない理由についての私の意見については、それは一般的に数値をソートしたり検索キーとしてそれらを使用する必要がない多くのドメインと関係があるかもしれません。ただし、私の個人的な経験だけに基づいて、以前の同僚の多くは、それが完全に適している場合、および一部には、それが動作するように作成できることを知らなかったために、それを使用していませんでしたFPとネガ。したがって、数値型でのみ機能することは別として、それは偶数であるとしばしば考えられますless実際よりも一般的に適用可能です。浮動小数点数や負の整数では機能しないと思っていたとしても、あまり使いません。

いくつかのベンチマーク:

Sorting 10000000 elements 3 times...

mt_sort_int: {0.135 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

mt_radix_sort: {0.228 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

std::sort: {1.697 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

qsort: {2.610 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

そして、それはちょうど私の素朴な実装の場合です(mt_sort_intも基数ソートですが、キーが整数であると想定できるので、コードのより高速なブランチを使用します)。専門家によって書かれた標準的な実装がどれほど速いか想像してみてください。

基数ソートがC++の非常に高速な比較ベースのstd::sortよりも悪いことに気付いた唯一のケースは、32などの非常に少数のエレメントの場合でした。そのとき、std::sortは、ヒープソートまたは挿入ソートですが、その時点では、実装ではstd::sortを使用しています。

4
user204677

もう1つの理由:最近のソートは、通常、コンパイラー提供のソートロジックに接続されたユーザー提供のソートルーチンで実装されています。基数ソートを使用すると、これはかなり複雑になり、ソートルーチンが可変長の複数のキーに作用するとさらに悪化します。 (名前、生年月日など)

実際には、基数ソートonceを実装しています。これはメモリが限られていた昔のことで、一度にすべてのデータをメモリに取り込むことができませんでした。つまり、データへのアクセス数はO(n) vs O(n log n)よりもはるかに重要でした。データごとに1つのパスを作成し、各レコードをビンに割り当てました(実際には何も移動せず、どのレコードがどのビンに入っているかのリストによって。)空ではないビンごとに(私のソートキーはテキストで、空のビンがたくさんあります)実際にデータをメモリに入れることができるかどうかを確認しました-はいの場合は、それを取り込み、クイックソートを使用します。いいえの場合は、ビン内のアイテムのみを含む一時ファイルを作成し、ルーチンを再帰的に呼び出します(実際には、少数のビンがオーバーフローします)。これにより、2つの完全な読み取りと1つの完全な書き込みが発生しました。ネットワークストレージに、この10%程度をローカルストレージに、単純にファイル全体をクイックソートすると、約2 * n log nの読み取りと約半分の書き込みが発生し、かなり低速になると思います。

最近では、このようなビッグデータの問題に遭遇するのははるかに困難です。おそらく、そのようなことを再び書くことはおそらくないでしょう。 (最近同じデータに直面した場合は、64ビットOSを指定するだけで、RAMを追加します。)

1
Loren Pechtel