基数ソートが頻繁に使用されないのはなぜですか?


31

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


2
こちらをご覧ください:en.wikipedia.org/wiki/Radix_sort#Efficiency効率はO(kn)であり、O(n * log(n))よりも良くないかもしれません。
FrustratedWithFormsDesigner

2
基数ソートは、ゲームなどのソフトリアルタイムシステムで頻繁に使用されます。かどうかは1つのアルゴリズムよりも優れ別は、いつものように、問題のすべてのパラメータに依存しているだけではなく、複雑さが拘束
awdz9nld

@FrustratedWithFormsDesignerおそらくwikiが変更されましたか?`n log(n)への参照がもうありません、FWIW ...
rogerdpack

ブーストはそれを(代わりにバリアント)があります。boost.org/doc/libs/1_62_0/libs/sort/doc/html/sort/sort_hpp.htmlをしかし、ええ、私は、人々はちょうどそれが存在しているかわからないと思います...それとも、すべてが単に「標準」ソートアルゴリズムを使用しますが、何らかの理由で、フレームワークの作成者は、効率的ではない「汎用」ソートを再利用する傾向があります...おそらくintのソートに焦点を合わせていません通常、それはまれなユースケースですか?
rogerdpack

回答:


38

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

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


記載されている複雑さに関する注意:(LSD)RadixソートはO(n * K)の複雑さを持ちますが、この定数は通常小さく、通常(2 ^(W / K))* CがL1に適合するように選択されます。はカウンターのバイト単位のサイズ、Wはソートされるキーのサイズです。ほとんどの実装では、x86の32ビットワードにK = [3,4]を選択します。Kは、各基数が個別にソートされるため、時間的コヒーレンス(ソートに近い)を活用するように適応させることもできます。
awdz9nld 14

11
普遍性に注意:基数ソート完全浮動小数点キーに動作することができるだけでなく、キーの整数の可変長である
awdz9nld

20

ほとんどのソートアルゴリズムは汎用です。比較関数を指定すると、それらは何でも動作し、QuicksortやHeapsortなどのアルゴリズムはO(1)の追加メモリでソートされます。

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

最後に、人々は一般的に、もはや独自のソートアルゴリズムを作成しません。ほとんどの言語には、並べ替えるライブラリ機能があり、通常は正しい言語を使用します。基数ソートは普遍的に適用可能ではなく、通常は実際の使用に合わせて調整する必要があり、多くの追加メモリを使用するため、ライブラリ関数またはテンプレートに入れるのは困難です。


実際、左右のパーティションで再帰呼び出しが行われるO(n^2)ため、最悪の場合、クイックソートにはメモリが必要nです。実装が末尾再帰最適化を使用している場合、O(n)適切なパーティションへの呼び出しに余分なスペースが必要ないため、これを下げることができます。(en.wikipedia.org/wiki/Quicksort#Space_complexity
カオスのスプリンター

S(n) \in O(n)基数でソートするためのスペースのみが必要です。つまり、ヒープまたはクイックソートと同じです。
ヴェルダ

@SplinterofChaosはおそらくwikiを変更しましたか?n^2クイックソートについてはもう言及していないようですが、O(log n)...
rogerdpack

私はそれが「はるかに」多くのメモリだとは思わない、たぶん2 * n(それはもっとたくさんあるが、不可能ではないかもしれない)?そして、バケットは非常に小さいので(バイトで分割して再帰していると仮定して)、キャッシュにうまく収まるでしょうか?
19:31のrogerdpack

5

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

それ以外の場合、基準は運用上のみ定義されます(2つのレコードが与えられた場合、どちらが最初になるかを決定できますが、分離されたレコードがどの程度スケールダウンするかを評価することはできません)。そのため、この方法は多くの場合、適用できないか、信じられないほど適用性が低いか、O(n * log(n))よりも高速ではありません。


基数ソートは、「一度に1バイトずつ」再帰的にソートすることで、任意の範囲の整数(または文字列)を処理できるため、FWIWのスパース範囲にある必要はありません...
rogerdpack

4

私は実際に比較ベースのソートよりも常にそれを使用していますが、私は明らかに、他の何よりも数字でよりうまく動作する奇妙なボールです(私はほとんど文字列では動作しません。並べ替えは、重複を除外して集合の交差を計算するのに再び役立ちます。辞書編集による比較は実際には行いません)。

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

私が提案したいことの1つは、基数ソートは浮動小数点数と負数に対して機能するということです。ただし、可能な限り移植性の高いFPバージョンを作成することは困難です。また、O(n * K)ですが、Kはキーサイズのバイト数である必要があります(例:100万の32ビット整数は、バケットに2 ^ 8エントリがある場合、通常4バイトサイズのパスを取得します)。メモリアクセスパターンは、通常、並列配列と小さなバケット配列を必要とする場合でも、クイックソートよりもはるかにキャッシュフレンドリーになる傾向があります(2番目の配列は通常、スタックにうまく収まります)。QSは、5000万のスワップを実行して、散発的なランダムアクセスパターンで100万の整数の配列をソートします。基数ソートは、データに対する4つの線形のキャッシュフレンドリーパスでこれを実行できます。

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

なぜ人々がそれをより頻繁に使用しないのかについての私の意見に関しては、それは一般的に数字を並べ替えたり検索キーとして使用する必要がない多くのドメインに関係しているかもしれません。しかし、私の個人的な経験に基づいて、以前の同僚の多くは、それが完全に適している場合、それがFPとネガで動作するようにできることを知らなかったため、それを使用しませんでした。そのため、数値型のみで機能することは別として、実際よりも一般的に適用されることは少ないと考えられています。浮動小数点数と負の整数で機能しないと思った場合も、ほとんど使用しません。

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

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ます。


1
その地域での経験を持つ人々の意見を聞くのはいつでもいい。
フランクヒルマン

mt_はマルチスレッド実装であると思われます:softwareengineering.stackexchange.com/a/362097/65606
rogerdpack

1

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

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

最近では、このようなビッグデータの問題に遭遇するのははるかに難しく、おそらくそのようなことは二度と書きません。(最近同じデータに直面した場合、単純に64ビットOSを指定し、そのエディターでスラッシングが発生した場合はRAMを追加します。)


時々言及される基数ソートについて言及された欠点の1つを考慮することは、「それはより多くのスペースを必要とします」です。まだこれに頭を
包も

1
@rogerdpackそれは私のアプローチがより少ないスペースを使用したことではなく、データへのより少ないアクセスを使用したことです。コードと64kbの構造制限を含む合計メモリ使用量が16 MB未満のコンパイラ制限(WindowsではなくDOS保護モード)を処理しながら、ギガバイト前後のファイルをソートしていました。
ローレンペクテル

-1

すべてのパラメーターが整数で、1024を超える入力パラメーターがある場合、基数ソートは常に高速です。

どうして?

Complexity of radix sort = max number of digits x number of input parameters.

Complexity of quick sort = log(number of input parameters) x   number of input parameters

したがって、基数ソートは次の場合に高速になります。

log(n)> max num of digits

Javaの最大整数は2147483647です。これは10桁の長さです

したがって、基数ソートは常に高速です

log(n)> 10

したがって、基数ソートは常に高速です n>1024


実装の詳細には隠された定数がありますが、基本的には「入力が大きいほど基数のソートが高速になります」ということになります。ユースケースを見つけるのは難しいですが、できるときは…
rogerdpack
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.