2016-09-30 4 views
3

私のJavaの教科書を使用すると、ランダムに任意の配列をシャッフルするには、次のコードを使用することができることを言う:配列をシャッフルする次のアルゴリズムに違いはありますか?

for(int i = myList.length-1; i >=0; i--) 
{ 

    int j = (int)(Math.random() * (i+1)); 
    double temp = myList[i]; 
    myList[i] = myList[j]; 
    myList[j] = temp; 

} 


でしょう私が書いた次のコードは、同じように効率的で有効なのでしょうか?

for(int i = 0; i < myList.length; i++) 
{ 

    int j = (int)(Math.random() * (myList.length)); 
    double temp = myList[i]; 
    myList[i] = myList[j]; 
    myList[j] = temp; 

} 

私のコードをテストして、要素を適切にシャッフルします。これ以上のテキストブックのアルゴリズムを使用する理由はありますか?

+3

そして、なぜ人々が答えを下降させているのか分かりません。投稿した人に申し訳ありません。彼らが落選した理由は何ですか? – AleksandrH

+8

あなたのコードは間違っていますが、それは正しく表示されます(シャッフル、一様ではありません)、それは古典的な[悪いシャッフルバイアス](http://stackoverflow.com/q/859253/555045) – harold

+1

@ AleksandrH投票数の多い方が間違っています。 (sigh)haroldのコメントが一番正しいです。 –

答えて

0

これ以上のテキストブックのアルゴリズムを使用する理由はありますか?

あなたのコードとテキストブックの両方が乗数の値に基づいたロケット科学を持っていません。コア部分はMath.random()関数です。あなたの本の乗数はjの値を繰り返し得る確率が数学的に低いi+1ですが、コードはちょうど繰り返しjの値を返す可能性が少し高いですが、率直に言っても問題ありません。

あなたのコードは、実際にはほんのわずかですが、実際には加算操作が実行されないたびに無視できるほど速くなります。

+1

唯一の違いは、本のバージョンでは、jのインデックスが0と現在のiの間のどこかに制限されているということです(私はあなたが繰り返しインデックスを得る可能性が低いと仮定しています)。長さ1になります(したがって、繰り返し値を取得する可能性があります)。各インデックスは(理想的には「ランダム」シャッフルで)選択される確率が等しいため、真にランダムなシャッフルで「my」アルゴリズムを使用しないでください。編集:うーん、おそらくない。思考は高く評価されています。 – AleksandrH

+0

はい、あなたは私の答えの理解について正しいです。真にランダムなシャッフルについては、乱数生成関数はまったくランダムではありません。興味深い読んで[ここ](http://programmers.stackexchange.com/questions/124233/why-is-it-impossible-to-produce-truly-random-numbers)。 –

3

第1の例は、要素がランダムに配置される方法の公平性を保証するために好ましい。 2番目の例では、要素はランダムですが、等しくランダムではありません。

最初の例は、Math.randomを使用しない最適化バージョンに基づいています。同じものであるCollections.shuffle

 for (int i=size; i>1; i--) 
      swap(list, i-1, rnd.nextInt(i)); 

から

Random rand = ... 
for(int i = myList.length-1; i > 0; i--) { 
    int j = rand.nextInt(i+1); 
    double temp = myList[i]; 
    myList[i] = myList[j]; 
    myList[j] = temp; 
} 

これは計算が少なくて済むような「ランダム性」を生成する必要がないため、かなり高速になる可能性があります。 doubleを生成するのは、0から9のような小数よりはるかに高価です。

ただし、最初の例ではこれを利用せず、Math.random()を呼び出しています。 nextInt(n)

+0

これを正しく読んでいる場合、最初の例では、配列内の同じインデックスに要素をシャッフルすることは不可能です。 2番目の例では、要素が元のインデックスに含まれる可能性があります。これは結果を大幅に変更しないのですか? – PrestonM

+1

@PrestonM'j

+1

@downvoterはコメントしますか? –

1

いいえ、本のアルゴリズムの代わりにアルゴリズムを使用することはできません。


説明:あなたの本のアルゴリズムは、最後の1から始めて、現在の要素から始まり、その後、[0, current]内のすべての要素から別の要素をピックアップし、それらを交換。この方法では、高いインデックスの要素は決して再び触れることはできません。しかし、それはまだそれ自身とスワップすることになります(通常です)

ただし、アルゴリズムでは、0i - 1の間のすべての可能なインデックスからスワップするランダムインデックスを生成しています。したがって、より高いインデックス要素は、シャッフル中に元の位置に戻すことができます。


次のコードは、書籍のアルゴリズムと同じではありません。これは、あなたの本のアルゴリズムの場合に可能な要素をそのまま残しません。

for (int i = myList.length - 1; i > 0; i--) { 
    int j = (int)(Math.random() * i); 
    swap(myList, i, j); 
} 

private void swap(double[] myList, int i, int j) { 
    double temp = myList[i]; 
    myList[i] = myList[j]; 
    myList[j] = temp; 
} 
+3

これは正しくありません。最初のalgoは、同じことを行うCollections.shuffle()と同じ場所に要素を残すことができます。 –

+0

@JoopEggenこれ以外は真実ではありません... –

+2

@PeterLawrey:あなたは正しい - 要素はそのまま残すことができます。しかし、最初のアルゴリズムは、OPのアルゴリズムとは異なり、シャッフルされた要素に再び触れません。 – displayName

4

はい、実際は異なっています。

最初のアルゴリズムは古典的なKnuth Shuffleの変形です。 このアルゴリズムでは、私たちの乱数生成器(Math.random())が理想的なものであれば、n! (n階乗)の可能性のある置換を等確率で使用する。


第2のアルゴリズムにはこのプロパティはありません。 たとえば、n = 3の場合、3つの結果があり、3で均等に割り切れません。3 = 27です。 = 6、可能な順列の数。 が実際に、ここでの結果(:12プログラム統計を生成する)の確率である

[0, 1, 2] 4/27 
[0, 2, 1] 5/27 
[1, 0, 2] 5/27 
[1, 2, 0] 5/27 
[2, 0, 1] 4/27 
[2, 1, 0] 4/27 

N = 4の場合、結果はさらに不均一である(例えば、プログラムは、統計を生成する:34):

[1, 0, 3, 2] has probability 15/256 
[3, 0, 1, 2] has probability 8/256 

あなたの順列が一様にランダムであると思われる場合、これは望ましくない性質です。


最後に、真のランダムソースではなく擬似乱数ジェネレータを通常使用するという事実は、上記のいずれかを無効にしません。 私たちの乱数ジェネレータの欠陥は、もしあれば、明らかに後のステップでダメージを修復することができません。

+0

詳細についてはhttp://stackoverflow.com/questions/859253/why-does-this-simple-shuffle-algorithm-produce-biased-results-what-is-a-simple +1すべて同じです。 –

関連する問題