web-dev-qa-db-ja.com

基数ソートはいつ使用する必要がありますか?

Radixソートの平均ケースパフォーマンスは非常に優れているようです。つまり、O(kN)http://en.wikipedia.org/ wiki/Radix_sort

しかし、ほとんどの人はまだクイックソートを使用しているようです。

40
Howard

基数ソートは、他のほとんどのソートアルゴリズムよりも一般化が困難です。固定サイズのキーと、キーを断片に分割する標準的な方法が必要です。したがって、それは決してライブラリへの道を見つけません。

27
Mark Ransom

あなたのコメントに従って編集:

  • 基数ソートは、整数、固定サイズ文字列、浮動小数点、および「より小さい」、「より大きい」または「辞書式順序」比較述語にのみ適用されますが、比較ソートは異なる順序に対応できます。
  • kはlog Nより大きくてもかまいません。
  • クイックソートはその場で実行できますが、基数ソートは効率が低下します。
17
Alexandre C.

ここでの他の答えは恐ろしいです、彼らは基数ソート実際に使用されているの例を示していません。

例は、スキューDC3アルゴリズム(Kärkkäinen-Sanders-Burkhardt)を使用して「接尾辞配列」を作成する場合です。アルゴリズムは、ソートアルゴリズムが線形時間である場合のみ線形時間であり、基数ソートは必要であり、キーは構造上短いため(3タプルの整数)、基数ソートが有用です。

16
Mehrdad

hugeリストまたは極端に小さいキーがない限り、log(N)は通常kより小さく、めったに高くならない。したがって、O(N log N)の平均ケースパフォーマンスを備えた汎用ソートアルゴリズムを選択することは、基数ソートを使用することより必ずしも悪くありません。

訂正:@Mehrdadがコメントで指摘したように、上記の引数は正しくありません:キーサイズが一定で、基数ソートがO(N)であるか、キーサイズがkである場合、クイックソートはO(k N log N)です。したがって、理論的には、基数ソートの方が漸近ランタイムが実際に優れています。

実際には、ランタイムは次のような用語に支配されます。

  • 基数ソート:c1 k N

  • クイックソート:c2 k N log(N)

ここで、c1 >> c2は、より長いキーからビットを「抽出」するのが通常ビットシフトと論理演算(または少なくとも非整列メモリアクセス)を伴う高価な操作であるのに対し、最新のCPUは64、128、または256ビットのキーを比較できるためです1つの操作で。したがって、多くの一般的なケースでは、Nが巨大でない限り、c1はc2 log(N)よりも大きくなります。

9
Niki

n> 128の場合、RadixSortを使用する必要があります

int32sを並べ替えるとき、基数256を選択するため、k = log(256、2 ^ 32)= 4であり、log(2、n)よりもかなり小さい

私のテストでは、基数ソートは、最良の場合のクイックソートより7倍高速です。

public class RadixSort {
    private static final int radix=256, shifts[]={8,16,24}, mask=radix-1;
    private final int bar[]=new int[radix];
    private int s[] = new int[65536];//不使用额外的数组t,提高cpu的cache命中率

    public void ensureSort(int len){
        if(s.length < len)
            s = new int[len];
    }   

    public void sort(int[] a){
        int n=a.length;
        ensureSort(n);
        for(int i=0;i<radix;i++)bar[i]=0;
        for(int i=0;i<n;i++)bar[a[i]&mask]++;//bar存放了桶内元素数量
        for(int i=1;i<radix;i++)bar[i]+=bar[i-1];//bar存放了桶内的各个元素在排序结果中的最大下标+1
        for(int i=0;i<n;i++)s[--bar[a[i]&mask]]=a[i];//对桶内元素,在bar中找到下标x=bar[slot]-1, 另s[x]=a[i](同时--bar[slot]将下标前移,供桶内其它元素使用)

        for(int i=0;i<radix;i++)bar[i]=0;
        for(int i=0;i<n;i++)bar[(s[i]>>8)&mask]++;
        for(int i=1;i<radix;i++)bar[i]+=bar[i-1];
        for(int i=n-1;i>=0;i--)a[--bar[(s[i]>>8)&mask]]=s[i];//同一个桶内的元素,低位已排序,而放入t中时是从t的大下标向小下标放入的,所以应该逆序遍历s[i]来保证原有的顺序不变

        for(int i=0;i<radix;i++)bar[i]=0;
        for(int i=0;i<n;i++)bar[(a[i]>>16)&mask]++;
        for(int i=1;i<radix;i++)bar[i]+=bar[i-1];
        for(int i=n-1;i>=0;i--)s[--bar[(a[i]>>16)&mask]]=a[i];//同一个桶内的元素,低位已排序,而放入t中时是从t的大下标向小下标放入的,所以应该逆序遍历s[i]来保证原有的顺序不变

        for(int i=0;i<radix;i++)bar[i]=0;
        for(int i=0;i<n;i++)bar[(s[i]>>24)&mask]++;
        for(int i=129;i<radix;i++)bar[i]+=bar[i-1];//bar[128~255]是负数,比正数小
        bar[0] += bar[255];
        for(int i=1;i<128;i++)bar[i]+=bar[i-1];     
        for(int i=n-1;i>=0;i--)a[--bar[(s[i]>>24)&mask]]=s[i];//同一个桶内的元素,低位已排序,而放入t中时是从t的大下标向小下标放入的,所以应该逆序遍历s[i]来保证原有的顺序不变      
    }
}
8
zhuwenbin

基数ソートにはO(k * n)時間かかります。しかし、Kとは何かを尋ねる必要があります。Kは「桁数」です(少し単純ですが、基本的にはそのようなものです)。

それで、あなたは何桁持っていますか?基数アルゴリズムをO(n log n)にするlog(n)(ベースとして「桁サイズ」を使用するログ)以上のかなりの答え。

何故ですか? log(n)桁未満の場合、n未満の可能性のある数字があります。したがって、O(n)時間(各番号の数を数えるだけです)を要する「カウントソート」を使用できます。したがって、k> log(n)桁...

それが、人々がRadixソートをそれほど使用しない理由です。使用する価値がある場合もありますが、ほとんどの場合、クイックソートの方がはるかに優れています。

8
Guy

k =「ソートされる配列の最長値の長さ」

n =「配列の長さ」

O(k * n)=「最悪の場合の実行」

k * n = n ^ 2(k = nの場合)

そのため、Radixソートを使用する場合は、「最長の整数が配列サイズよりも短い」こと、またはその逆を確認してください。次に、クイックソートを倒します!

欠点は次のとおりです。ほとんどの場合、整数がどれだけ大きくなるかを保証することはできませんが、数値の範囲が固定されている場合、基数ソートを使用する必要があります。

3
kiltek

クイックソートと基数ソートを比較するリンクは次のとおりです。

整数配列の基数ソートはクイックソートよりも高速ですか? (そうです、2-3x)

いくつかのアルゴリズムの実行時間を分析する別のリンクを次に示します。

並べ替えの質問

同じデータではどちらが高速ですか。 an O(n) sortまたはan O(nLog(n)) sort?

回答:状況によります。ソートされるデータの量に依存します。それが実行されているハードウェアに依存し、アルゴリズムの実装に依存します。

2
johndoevodka

基数の並べ替えは比較ベースの並べ替えではなく、整数(ポインタアドレスを含む)や浮動小数点などの数値型のみを並べ替えることができ、浮動小数点を移植可能にサポートするのは少し困難です。

これはおそらく、適用範囲が非常に狭いために、多くの標準ライブラリがそれを省略することを選択したためです。一部の人々は、整数をソートのキーとして使用される他の何かへのインデックスとして使用するほど整数を直接ソートしたくない場合があるため、独自のコンパレータを提供することさえできません。比較ベースの並べ替えはすべての柔軟性を可能にするので、おそらく1%に対応するのではなく、人々の日常のニーズの99%に適合する汎用ソリューションを好む場合です。

そうは言っても、適用範囲が狭いにもかかわらず、私のドメインでは、イントロソートやクイックソートよりも基数ソートの方が多く使用されています。私はその1%におり、たとえば文字列キーをほとんど使用していませんが、多くの場合、並べ替えの恩恵を受ける数字のユースケースを見つけます。これは、コードベースが、エンティティとコンポーネント(エンティティコンポーネントシステム)のインデックスだけでなく、インデックス付きメッシュのようなものを中心に展開し、大量の数値データがあるためです。

その結果、基数ソートは、私の場合、あらゆる種類のものに役立ちます。私の場合の一般的な例の1つは、重複するインデックスを削除することです。その場合、結果をソートする必要はありませんが、多くの場合、基数ソートを使用すると、代替よりも速く重複を削除できます。

別の方法は、たとえば、特定の次元に沿ったkdツリーの中央分離帯を見つけることです。与えられた次元のポイントの浮動小数点値を基数でソートすると、ツリーノードを分割するために線形時間で迅速に中央位置が得られます。

もう1つは、フラグシェーダーで行わない場合に、半適切なアルファ透明度を得るために、zによって高レベルのプリミティブを深度ソートすることです。また、GUIおよびzオーダー要素に対するベクトルグラフィックソフトウェアにも適用されます。

もう1つは、インデックスのリストを使用したキャッシュフレンドリーな順次アクセスです。インデックスが何度もトラバースされる場合、事前に基数をソートすると、トラバーサルがランダムな順序ではなくシーケンシャルな順序で実行されるようになり、パフォーマンスが向上することがよくあります。後者は、メモリ内でジグザグに往復し、同じループ内で同じメモリ領域を繰り返しリロードするためにのみキャッシュラインからデータを排除します。繰り返しアクセスする前に最初にインデックスを基数で並べ替えると、それが停止し、キャッシュミスを大幅に減らすことができます。これは実際に基数ソートの最も一般的な使用方法であり、システムが2つ以上のコンポーネントを持つエンティティにアクセスする場合、ECSがキャッシュフレンドリーであることの鍵となります。

私の場合、私は非常に頻繁に使用するマルチスレッド基数ソートを使用しています。いくつかのベンチマーク:

--------------------------------------------
- test_mt_sort
--------------------------------------------
Sorting 1,000,000 elements 32 times...

mt_radix_sort: {0.234000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]

std::sort: {1.778000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]

qsort: {2.730000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]

6-7ミリ秒のようなものを平均して、ちょっとしたハードウェアで一度に100万個の数字を並べ替えることができます。 C++のstd::sortまたはCのqsortは、フレームレートの非常に明らかなしゃっくりを確実にもたらします。 SIMDを使用して基数の並べ替えを実装している人々のことを聞いたことがありますが、それをどのように管理したかはわかりません。私はそのような解決策を思い付くほど賢くはありませんが、私の素朴な小さな基数のソートでさえ、標準ライブラリと比較して非常にうまく機能します。

2
Dragon Energy

1つの例は、非常に大きな整数のセットまたは配列をソートする場合です。基数ソートおよびその他のタイプの分散ソートは、データ要素が主にキューの配列(LSD基数ソートの最大10キュー)にキューイングされ、ソートされる同じ入力データの異なるインデックス位置に再マッピングされるため、非常に高速です。ネストされたループがないため、ソートされるデータ入力整数の数が大幅に増えると、アルゴリズムはより直線的に動作する傾向があります。非常に非効率的なbubbleSortメソッドのような他のソート方法とは異なり、基数ソートはソートのための比較演算を実装しません。入力が最終的にソートされるまで、整数を異なるインデックス位置に再マッピングする単純なプロセスです。 LSD基数の並べ替えを自分でテストする場合は、1つを書き、githubに保存しました。これは、雄弁なJavaScriptのコーディングサンドボックスなどのオンラインjs ideで簡単にテストできます。自由に試してみて、異なる数のnでどのように動作するかを見てください。ランタイムが300ミリ秒未満の最大900,000の未ソート整数でテストしました。あなたがそれで遊んでみたいなら、ここにリンクがあります。

https://Gist.github.com/StBean/4af58d09021899f14dfa585df6c86df6

0