2011年4月6日水曜日

重複なしの乱数発生

重複なしの乱数を発生するための例題です。

重複なしの乱数発生について、本来ならば一度発生した乱数を配列に登録していき、すでに登録されている乱数が発生されればその乱数は破棄する等の手順をとりますが、そうすると、必要な乱数の数よりも多い計算回数を必要とします。

そこで、乱数発生の数を必要な数と同じにし、判定の手順も必要としないアルゴリズムを考えてみました。

下記の例題にて、
int round_tbl[RAND_N]; // 変換テーブル
は、発生した乱数を使われていない値に変換するためのテーブルであり、必要な乱数の数(RAND_N)を次第に減らしていくために利用しています。
全ての値が発生すれば、必要な乱数の数は 0 になります。

このアルゴリズムでは、変換テーブル用の配列の準備が必要となります(その分のメモリが必要)。
乱数を発生する数は、必要な乱数の数と同じであり、不必要な乱数を破棄することはありませんので、高速なアルゴリズムになります。

例題では、通常のアルゴリズムと比較した時間も表示しています。
10000 の数を発生する際には16倍程度高速になっています。

図解begin
 _______________
|0|1|2|3|・・|n−1| → n個の配列
  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
最大 n の乱数を発生 val=2 → 配列から 2番目の値を抜き出し、抜けた分を詰める
 ______________
|0|1|3|4|・|n−1| → n-1個の配列
  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
最大 n-1 の乱数を発生し、上記と同じように発した値の配列を抜き出し、抜けた分を詰める

以下、配列がなくなるまで繰り返す
図解end

クラス化し、必要な値だけ取り出すように変更した例は こちら

実行結果

3 件のコメント:

  1. line 66で、結果、val == rn-1 の場合、変換テーブルの更新は不要だと思いますが???

    返信削除
    返信
    1. 仰るとおり、
      if ( val != rn-1 ) {
      for ( i=val; i<rn; i++ ) round_tbl[i] = round_tbl[i+1];
      }
      で大丈夫ですね。また修正しておきます。

      削除
    2. <2013年10月21日 17:25> の者です。
      ほんとクールなコードですね。
      こちらを見つけるまで、先頭からちくちく探索していました。
      どうもありがとうございました。

      削除