web-dev-qa-db-ja.com

O(1)の一意の(繰り返しのない)乱数?

私は0から1000までのユニークな乱数を生成したいと思いますが、これは繰り返されません(6は2回現れません)が、それは前のO(N)検索のようなものに頼りませんそれを行うための値。これは可能ですか?

173
dicroce

値0〜1000で1001個の整数の配列を初期化し、変数maxを配列の現在の最大インデックス(1000から始まる)に設定します。 0からmaxの間の乱数rを選択し、rの位置の数値をmaxの位置の数値と交換し、現在のmaxの位置の数値を返します。最大値を1減らして続行します。 maxが0の場合、maxを配列のサイズ-1に戻し、配列を再初期化せずに再起動します。

更新:質問に答えたときに自分でこの方法を思いつきましたが、いくつかの調査の後、これは Fisher-Yates Durstenfeld-Fisher-YatesまたはKnuth-Fisher-Yatesとして知られています。説明を理解するのは少し難しいかもしれないので、以下に例を示します(1001の代わりに11個の要素を使用)。

配列はarray [n] = nに初期化された11個の要素から始まり、最大は10から始まります。

+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
+--+--+--+--+--+--+--+--+--+--+--+
                                ^
                               max    

各反復で、乱数rが0〜maxの間で選択され、array [r]とarray [max]が交換され、新しいarray [max]が返され、maxが減少します。

max = 10, r = 3
           +--------------------+
           v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 7| 8| 9| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 9, r = 7
                       +-----+
                       v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 9| 8| 7: 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 8, r = 1
     +--------------------+
     v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 5| 6| 9| 1: 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 7, r = 5
                 +-----+
                 v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 9| 6| 5: 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

...

11回の反復後、配列内のすべての数値が選択され、max == 0になり、配列要素がシャッフルされます。

+--+--+--+--+--+--+--+--+--+--+--+
| 4|10| 8| 6| 2| 0| 9| 5| 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

この時点で、maxは10にリセットでき、プロセスを続行できます。

238
Robert Gamble

あなたはこれを行うことができます:

  1. リスト0..1000を作成します。
  2. リストをシャッフルします。 (これを行う良い方法については Fisher-Yates shuffle をご覧ください。)
  3. シャッフルされたリストから順番に番号を返します。

そのため、毎回古い値を検索する必要はありませんが、最初のシャッフルにはO(N)が必要です。しかし、Nilsがコメントで指摘したように、これは償却されたO(1)です。

72

最大線形フィードバックシフトレジスタ を使用します。

Cの数行で実装可能であり、実行時に2つのテスト/ブランチ、少しの追加、ビットシフトを実行するだけです。それはランダムではありませんが、ほとんどの人をだます。

58
plinth

A Linear Congruential Generator を使用できます。 m(モジュラス)は、1000より大きい最も近い素数になります。範囲外の数値を取得したら、次の素数を取得します。シーケンスはすべての要素が発生した後にのみ繰り返され、テーブルを使用する必要はありません。ただし、このジェネレーターの欠点に注意してください(ランダム性の欠如を含む)。

20
Paul de Vrieze

Format-Preserving Encryption を使用して、カウンターを暗号化できます。カウンターは0から上に移動するだけで、暗号化では選択したキーを使用して、基数と幅が任意のランダムな値に変換されます。例えば。この質問の例では、基数10、幅3で​​す。

通常、ブロック暗号のブロックサイズは固定です。 64または128ビット。ただし、Format-Preserving Encryptionを使用すると、AESのような標準暗号を使用して、暗号化に堅牢なアルゴリズムを使用して、必要な基数と幅の幅の狭い暗号を作成できます。

衝突が発生しないことが保証されています(暗号化アルゴリズムは1:1マッピングを作成するため)。また、リバーシブル(2ウェイマッピング)であるため、結果の数値を取得して、開始時のカウンター値に戻すことができます。

この手法は、シャッフルされた配列などを保存するためのメモリを必要としません。これは、メモリが限られているシステムで利点となる可能性があります。

AES-FFX は、これを達成するために提案された標準的な方法の1つです。 AES-FFXのアイデアに基づいたいくつかの基本的なPythonコードを試しましたが、完全に準拠しているわけではありませんが、- ここでPythonコードを参照してください 。例えばカウンターをランダムに見える7桁の10進数、または16ビットの数値に暗号化します。基数10、幅3(0から999までの数字を与える)の例は次のとおりです。

000   733
001   374
002   882
003   684
004   593
005   578
006   233
007   811
008   072
009   337
010   119
011   103
012   797
013   257
014   932
015   433
...   ...

異なる非繰り返し擬似ランダムシーケンスを取得するには、暗号化キーを変更します。各暗号化キーは、異なる非反復擬似ランダムシーケンスを生成します。

17
Craig McQueen

0 ... 1000のような低い数値の場合、すべての数値を含むリストを作成してシャッフルするのは簡単です。ただし、描画する数値のセットが非常に大きい場合、別のエレガントな方法があります。キーと暗号化ハッシュ関数を使用して、擬似ランダムな順列を構築できます。次のC++風の擬似コードの例を参照してください。

unsigned randperm(string key, unsigned bits, unsigned index) {
  unsigned half1 =  bits    / 2;
  unsigned half2 = (bits+1) / 2;
  unsigned mask1 = (1 << half1) - 1;
  unsigned mask2 = (1 << half2) - 1;
  for (int round=0; round<5; ++round) {
    unsigned temp = (index >> half1);
    temp = (temp << 4) + round;
    index ^= hash( key + "/" + int2str(temp) ) & mask1;
    index = ((index & mask2) << half1) | ((index >> half2) & mask1);
  }
  return index;
}

ここで、hashは、文字列を巨大な符号なし整数にマッピングする、任意の疑似ランダム関数です。関数randpermは、固定キーを想定した0 ... pow(2、bits)-1内のすべての数値の順列です。これは、変数indexを変更するすべてのステップが可逆的であるため、構築から始まります。これは Feistel暗号 に触発されています。

7
sellibitze

ここで説明する私のXincrolアルゴリズムを使用できます。

http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html

これは、配列、リスト、順列、または重いCPU負荷なしで、ランダムだが一意の数値を生成する純粋なアルゴリズム手法です。

最新バージョンでは、数値の範囲を設定することもできます。たとえば、0-1073741821の範囲で一意の乱数が必要な場合。

私は実際にそれを使用しました

  • すべての曲をランダムに再生しますが、アルバム/ディレクトリごとに1回だけ再生するMP3プレーヤー
  • ピクセル単位のビデオフレーム溶解効果(高速でスムーズ)
  • 署名およびマーカーの画像上に秘密の「ノイズ」フォグを作成する(ステガノグラフィー)
  • データベースを介して大量のJavaオブジェクトをシリアル化するためのデータオブジェクトID
  • トリプルマジョリティメモリビット保護
  • アドレス+値の暗号化(すべてのバイトは暗号化されるだけでなく、バ​​ッファ内の新しい暗号化された場所に移動されます)。これは本当に暗号解読の仲間を怒らせた:-)
  • SMS、電子メールなどの平文から平文のような暗号化テキスト暗号化.
  • テキサスホールデムポーカー電卓(THC)
  • シミュレーション、「シャッフル」、ランキング用の私のゲームのいくつか
  • もっと

オープンで無料です。試してみる...

6
Tod Samay

これを解決するために配列さえ必要ありません。

ビットマスクとカウンターが必要です。

カウンターをゼロに初期化し、連続する呼び出しで増分します。 XOR擬似乱数を生成するためのビットマスク付きカウンター(起動時にランダムに選択、または固定)。 1000を超える数値を使用できない場合は、9ビットよりも広いビットマスクを使用しないでください。 (つまり、ビットマスクは511を超えない整数です。)

カウンタが1000を超えたら、必ずゼロにリセットしてください。この時点で、必要に応じて別のランダムビットマスクを選択して、同じ番号のセットを異なる順序で生成できます。

5
Max

最初のソリューションのロジックを使用する、入力したコードを次に示します。私はこれが「言語に依存しない」ことを知っていますが、誰もが迅速で実用的な解決策を探している場合に備えて、これをC#の例として提示したかっただけです。

// Initialize variables
Random RandomClass = new Random();
int RandArrayNum;
int MaxNumber = 10;
int LastNumInArray;
int PickedNumInArray;
int[] OrderedArray = new int[MaxNumber];      // Ordered Array - set
int[] ShuffledArray = new int[MaxNumber];     // Shuffled Array - not set

// Populate the Ordered Array
for (int i = 0; i < MaxNumber; i++)                  
{
    OrderedArray[i] = i;
    listBox1.Items.Add(OrderedArray[i]);
}

// Execute the Shuffle                
for (int i = MaxNumber - 1; i > 0; i--)
{
    RandArrayNum = RandomClass.Next(i + 1);         // Save random #
    ShuffledArray[i] = OrderedArray[RandArrayNum];  // Populting the array in reverse
    LastNumInArray = OrderedArray[i];               // Save Last Number in Test array
    PickedNumInArray = OrderedArray[RandArrayNum];  // Save Picked Random #
    OrderedArray[i] = PickedNumInArray;             // The number is now moved to the back end
    OrderedArray[RandArrayNum] = LastNumInArray;    // The picked number is moved into position
}

for (int i = 0; i < MaxNumber; i++)                  
{
    listBox2.Items.Add(ShuffledArray[i]);
}
3
firedrawndagger

このメソッドは、制限がhighであり、少数の乱数のみを生成する場合に適切になります。

#!/usr/bin/Perl

($top, $n) = @ARGV; # generate $n integer numbers in [0, $top)

$last = -1;
for $i (0 .. $n-1) {
    $range = $top - $n + $i - $last;
    $r = 1 - Rand(1.0)**(1 / ($n - $i));
    $last += int($r * $range + 1);
    print "$last ($r)\n";
}

番号は昇順で生成されますが、その後シャッフルできることに注意してください。

3
salva

良い 疑似乱数ジェネレーター を10ビットで使用し、1001から1023を捨てて0から1000を残すことができます。

here から、10ビットPRNGの設計を取得します。

  • 10ビット、フィードバック多項式x ^ 10 + x ^ 7 + 1(期間1023)

  • ガロアLFSRを使用して高速コードを取得する

2
pro

Linear congruential generator が最も簡単な解決策だと思います。

enter image description here

acおよびm

  1. mおよびcは比較的素数で、
  2. a-1は、m
  3. a-14で割り切れるifm4

PSこのメソッドについてはすでに言及しましたが、投稿には定数値に関する誤った仮定があります。以下の定数はあなたの場合にうまく機能するはずです

あなたの場合、a = 1002c = 757m = 1001を使用できます

X = (1002 * X + 757) mod 1001
2
Max Abramovich
public static int[] randN(int n, int min, int max)
{
    if (max <= min)
        throw new ArgumentException("Max need to be greater than Min");
    if (max - min < n)
        throw new ArgumentException("Range needs to be longer than N");

    var r = new Random();

    HashSet<int> set = new HashSet<int>();

    while (set.Count < n)
    {
        var i = r.Next(max - min) + min;
        if (!set.Contains(i))
            set.Add(i);
    }

    return set.ToArray();
}

N非反復乱数は、必要に応じてO(n)複雑度になります。
注:ランダムは、スレッドセーフが適用された静的でなければなりません。

2
Erez Robinson

質問 と上限Nの間のK個の非反復整数のリストを効率的に生成するにはどうすればよいですか は重複としてリンクされています-そして、O(1)生成された乱数ごとに(O(n)起動コストなしで)受け入れられた答えの簡単な調整があります。

初期化された配列を使用する代わりに、整数から整数への空の順序なしマップを作成します(空の順序付きマップは要素ごとにO(log k)を取ります)。最大の場合は、最大を1000に設定します。

  1. 0から最大の乱数rを選択します。
  2. マップ要素rとmaxの両方が順序付けられていないマップに存在することを確認してください。存在しない場合は、インデックスに等しい値で作成します。
  3. 要素rとmaxを入れ替える
  4. 要素maxを返し、maxを1減らします(maxが負になる場合は完了です)。
  5. 手順1に戻ります。

初期化された配列を使用する場合と比較した唯一の違いは、要素の初期化が延期/スキップされることですが、同じPRNGからまったく同じ数を生成します。

2
Hans Olsson

シャッフルされたリストを何度も何度も繰り返して、もう一度シャッフルするために最初からやり直すたびにO(n)の遅延を発生させたくない場合、次のようにします。

  1. 0〜1000の2つのリストAとBを作成し、2nスペースを取ります。

  2. Fisher-Yatesを使用してリストAをシャッフルするには、n時間かかります。

  3. 数字を描くときは、もう一方のリストで1ステップのFisher-Yatesシャッフルを行います。

  4. カーソルがリストの最後にあるとき、他のリストに切り替えます。

前処理

cursor = 0

selector = A
other    = B

shuffle(A)

描画

temp = selector[cursor]

swap(other[cursor], other[random])

if cursor == N
then swap(selector, other); cursor = 0
else cursor = cursor + 1

return temp
2
Khaled.K

試してみることのできるサンプルCOBOLコードを次に示します。

   IDENTIFICATION DIVISION.
   PROGRAM-ID.  RANDGEN as "ConsoleApplication2.RANDGEN".
   AUTHOR.  Myron D Denson.
   DATE-COMPILED.
  * ************************************************************** 
  *  SUBROUTINE TO GENERATE RANDOM NUMBERS THAT ARE GREATER THAN
  *    ZERO AND LESS OR EQUAL TO THE RANDOM NUMBERS NEEDED WITH NO
  *    DUPLICATIONS.  (CALL "RANDGEN" USING RANDGEN-AREA.)
  *     
  *  CALLING PROGRAM MUST HAVE A COMPARABLE LINKAGE SECTION
  *    AND SET 3 VARIABLES PRIOR TO THE FIRST CALL IN RANDGEN-AREA     
  *
  *    FORMULA CYCLES THROUGH EVERY NUMBER OF 2X2 ONLY ONCE. 
  *    RANDOM-NUMBERS FROM 1 TO RANDOM-NUMBERS-NEEDED ARE CREATED 
  *    AND PASSED BACK TO YOU.
  *
  *  RULES TO USE RANDGEN:
  *
  *    RANDOM-NUMBERS-NEEDED > ZERO 
  *     
  *    COUNT-OF-ACCESSES MUST = ZERO FIRST TIME CALLED.
  *         
  *    RANDOM-NUMBER = ZERO, WILL BUILD A SEED FOR YOU
  *    WHEN COUNT-OF-ACCESSES IS ALSO = 0 
  *     
  *    RANDOM-NUMBER NOT = ZERO, WILL BE NEXT SEED FOR RANDGEN
  *    (RANDOM-NUMBER MUST BE <= RANDOM-NUMBERS-NEEDED)       
  *     
  *    YOU CAN PASS RANDGEN YOUR OWN RANDOM-NUMBER SEED
  *     THE FIRST TIME YOU USE RANDGEN.
  *     
  *    BY PLACING A NUMBER IN RANDOM-NUMBER FIELD
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER > ZERO AND 
  *        RANDOM-NUMBER <= RANDOM-NUMBERS-NEEDED
  *       
  *    YOU CAN LET RANDGEN BUILD A SEED FOR YOU
  *     
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER = ZERO AND 
  *        RANDOM-NUMBER-NEEDED > ZERO  
  *         
  *     TO INSURING A DIFFERENT PATTERN OF RANDOM NUMBERS
  *        A LOW-RANGE AND HIGH-RANGE IS USED TO BUILD
  *        RANDOM NUMBERS.
  *        COMPUTE LOW-RANGE =
  *             ((SECONDS * HOURS * MINUTES * MS) / 3).         
  *        A HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE
  *        AFTER RANDOM-NUMBER-BUILT IS CREATED 
  *        AND IS BETWEEN LOW AND HIGH RANGE
  *        RANDUM-NUMBER = RANDOM-NUMBER-BUILT - LOW-RANGE
  *               
  * **************************************************************         
   ENVIRONMENT DIVISION.
   INPUT-OUTPUT SECTION.
   FILE-CONTROL.
   DATA DIVISION.
   FILE SECTION.
   WORKING-STORAGE SECTION.
   01  WORK-AREA.
       05  X2-POWER                     PIC 9      VALUE 2. 
       05  2X2                          PIC 9(12)  VALUE 2 COMP-3.
       05  RANDOM-NUMBER-BUILT          PIC 9(12)  COMP.
       05  FIRST-PART                   PIC 9(12)  COMP.
       05  WORKING-NUMBER               PIC 9(12)  COMP.
       05  LOW-RANGE                    PIC 9(12)  VALUE ZERO.
       05  HIGH-RANGE                   PIC 9(12)  VALUE ZERO.
       05  YOU-PROVIDE-SEED             PIC X      VALUE SPACE.
       05  RUN-AGAIN                    PIC X      VALUE SPACE.
       05  PAUSE-FOR-A-SECOND           PIC X      VALUE SPACE.   
   01  SEED-TIME.
       05  HOURS                        PIC 99.
       05  MINUTES                      PIC 99.
       05  SECONDS                      PIC 99.
       05  MS                           PIC 99. 
  *
  * LINKAGE SECTION.
  *  Not used during testing  
   01  RANDGEN-AREA.
       05  COUNT-OF-ACCESSES            PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBERS-NEEDED        PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBER                PIC 9(12) VALUE ZERO.
       05  RANDOM-MSG                   PIC X(60) VALUE SPACE.
  *    
  * PROCEDURE DIVISION USING RANDGEN-AREA.
  * Not used during testing 
  *  
   PROCEDURE DIVISION.
   100-RANDGEN-EDIT-Housekeeping.
       MOVE SPACE TO RANDOM-MSG. 
       IF RANDOM-NUMBERS-NEEDED = ZERO
         DISPLAY 'RANDOM-NUMBERS-NEEDED ' NO ADVANCING
         ACCEPT RANDOM-NUMBERS-NEEDED.
       IF RANDOM-NUMBERS-NEEDED NOT NUMERIC 
         MOVE 'RANDOM-NUMBERS-NEEDED NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF RANDOM-NUMBERS-NEEDED = ZERO
         MOVE 'RANDOM-NUMBERS-NEEDED = ZERO' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES NOT NUMERIC
         MOVE 'COUNT-OF-ACCESSES NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES GREATER THAN RANDOM-NUMBERS-NEEDED
         MOVE 'COUNT-OF-ACCESSES > THAT RANDOM-NUMBERS-NEEDED'
           TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF YOU-PROVIDE-SEED = SPACE AND RANDOM-NUMBER = ZERO
         DISPLAY 'DO YOU WANT TO PROVIDE SEED  Y OR N: '
           NO ADVANCING
           ACCEPT YOU-PROVIDE-SEED.  
       IF RANDOM-NUMBER = ZERO AND
          (YOU-PROVIDE-SEED = 'Y' OR 'y')
         DISPLAY 'ENTER SEED ' NO ADVANCING
         ACCEPT RANDOM-NUMBER. 
       IF RANDOM-NUMBER NOT NUMERIC
         MOVE 'RANDOM-NUMBER NOT NUMERIC' TO RANDOM-MSG
         GO TO 900-EXIT-RANDGEN.
   200-RANDGEN-DATA-Housekeeping.      
       MOVE FUNCTION CURRENT-DATE (9:8) TO SEED-TIME.
       IF COUNT-OF-ACCESSES = ZERO
         COMPUTE LOW-RANGE =
                ((SECONDS * HOURS * MINUTES * MS) / 3).
       COMPUTE RANDOM-NUMBER-BUILT = RANDOM-NUMBER + LOW-RANGE.  
       COMPUTE HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE.
       MOVE X2-POWER TO 2X2.             
   300-SET-2X2-DIVISOR.
       IF 2X2 < (HIGH-RANGE + 1) 
          COMPUTE 2X2 = 2X2 * X2-POWER
           GO TO 300-SET-2X2-DIVISOR.    
  * *********************************************************         
  *  IF FIRST TIME THROUGH AND YOU WANT TO BUILD A SEED.    *
  * ********************************************************* 
       IF COUNT-OF-ACCESSES = ZERO AND RANDOM-NUMBER = ZERO
          COMPUTE RANDOM-NUMBER-BUILT =
                ((SECONDS * HOURS * MINUTES * MS) + HIGH-RANGE).
       IF COUNT-OF-ACCESSES = ZERO        
         DISPLAY 'SEED TIME ' SEED-TIME 
               ' RANDOM-NUMBER-BUILT ' RANDOM-NUMBER-BUILT 
               ' LOW-RANGE  ' LOW-RANGE.          
  * *********************************************     
  *    END OF BUILDING A SEED IF YOU WANTED TO  * 
  * *********************************************               
  * ***************************************************
  * THIS PROCESS IS WHERE THE RANDOM-NUMBER IS BUILT  *  
  * ***************************************************   
   400-RANDGEN-FORMULA.
       COMPUTE FIRST-PART = (5 * RANDOM-NUMBER-BUILT) + 7.
       DIVIDE FIRST-PART BY 2X2 GIVING WORKING-NUMBER 
         REMAINDER RANDOM-NUMBER-BUILT. 
       IF RANDOM-NUMBER-BUILT > LOW-RANGE AND
          RANDOM-NUMBER-BUILT < (HIGH-RANGE + 1)
         GO TO 600-RANDGEN-CLEANUP.
       GO TO 400-RANDGEN-FORMULA.
  * *********************************************     
  *    GOOD RANDOM NUMBER HAS BEEN BUILT        *               
  * *********************************************
   600-RANDGEN-CLEANUP.
       ADD 1 TO COUNT-OF-ACCESSES.
       COMPUTE RANDOM-NUMBER = 
            RANDOM-NUMBER-BUILT - LOW-RANGE. 
  * *******************************************************
  * THE NEXT 3 LINE OF CODE ARE FOR TESTING  ON CONSOLE   *  
  * *******************************************************
       DISPLAY RANDOM-NUMBER.
       IF COUNT-OF-ACCESSES < RANDOM-NUMBERS-NEEDED
        GO TO 100-RANDGEN-EDIT-Housekeeping.     
   900-EXIT-RANDGEN.
       IF RANDOM-MSG NOT = SPACE
        DISPLAY 'RANDOM-MSG: ' RANDOM-MSG.
        MOVE ZERO TO COUNT-OF-ACCESSES RANDOM-NUMBERS-NEEDED RANDOM-NUMBER. 
        MOVE SPACE TO YOU-PROVIDE-SEED RUN-AGAIN.
       DISPLAY 'RUN AGAIN Y OR N '
         NO ADVANCING.
       ACCEPT RUN-AGAIN.
       IF (RUN-AGAIN = 'Y' OR 'y')
         GO TO 100-RANDGEN-EDIT-Housekeeping.
       ACCEPT PAUSE-FOR-A-SECOND.
       GOBACK.
1
Myron Denson

別の可能性:

フラグの配列を使用できます。そして、それがすでに選択されている場合、次のものを取ります。

ただし、1000回の呼び出し後は注意してください。関数は終了しないため、安全対策を講じる必要があります。

1
Toon Krijthe

Nが1000より大きく、K個のランダムサンプルを描画する必要がある場合、これまでのサンプルを含むセットを使用できます。描画ごとに 拒否サンプリング を使用します。これは「ほぼ」O(1)操作になるため、合計実行時間はほぼO(K)です。 _ O(N)ストレージ付き。

このアルゴリズムは、KがNに「近い」場合に衝突します。これは、実行時間がO(K)よりもはるかに悪いことを意味します。単純な修正方法は、ロジックを逆にして、K> N/2の場合、まだ描画されていないすべてのサンプルの記録を保持することです。各描画は、拒否セットからサンプルを削除します。

拒否サンプリングのもう1つの明らかな問題は、それがO(N)ストレージであるということです。これは、Nが数十億以上の場合、悪いニュースです。ただし、その問題を解決するアルゴリズムがあります。このアルゴリズムは、発明者にちなんでVitterのアルゴリズムと呼ばれます。アルゴリズムは ここ で説明されています。 Gist of Vitterのアルゴリズムでは、各描画の後に、一定のサンプリングを保証する特定の分布を使用してランダムスキップを計算します。

1

ここでの回答のほとんどは、同じ数値を2回返さないことを保証するものではありません。正しい解決策は次のとおりです。

int nrrand(void) {
  static int s = 1;
  static int start = -1;
  do {
    s = (s * 1103515245 + 12345) & 1023;
  } while (s >= 1001);
  if (start < 0) start = s;
  else if (s == start) abort();

  return s;
}

制約が適切に指定されているかどうかはわかりません。他の1000の出力の後に値を繰り返すことができると仮定しますが、1000のセットの最後と最初の両方に現れる限り、0の直後に0が続くことを単純に許可します。逆に、繰り返しの間に他の1000個の値があると、その制限を超えて発生した他の値がないため、シーケンスが毎回まったく同じようにリプレイする状況が強制されます。

値を繰り返す前に、少なくとも500個の他の値を常に保証する方法を次に示します。

int nrrand(void) {
  static int h[1001];
  static int n = -1;

  if (n < 0) {
    int s = 1;
    for (int i = 0; i < 1001; i++) {
      do {
        s = (s * 1103515245 + 12345) & 1023;
      } while (s >= 1001);
      /* If we used `i` rather than `s` then our early results would be poorly distributed. */
      h[i] = s;
    }
    n = 0;
  }

  int i = Rand(500);
  if (i != 0) {
      i = (n + i) % 1001;
      int t = h[i];
      h[i] = h[n];
      h[n] = t;
  }
  i = h[n];
  n = (n + 1) % 1001;

  return i;
}
1
sh1

Fisher Yates

for i from n−1 downto 1 do
     j ← random integer such that 0 ≤ j ≤ i
     exchange a[j] and a[i]

実際にはO(n-1)です
これはC#です

public static List<int> FisherYates(int n)
{
    List<int> list = new List<int>(Enumerable.Range(0, n));
    Random Rand = new Random();
    int swap;
    int temp;
    for (int i = n - 1; i > 0; i--)
    {
        swap = Rand.Next(i + 1);  //.net Rand is not inclusive
        if(swap != i)  // it can stay in place - if you force a move it is not a uniform shuffle
        {
            temp = list[i];
            list[i] = list[swap];
            list[swap] = temp;
        }
    }
    return list;
}
0
paparazzo

https://stackoverflow.com/a/46807110/8794687 で私の答えをご覧ください

これは、平均時間の複雑さを持つ最も単純なアルゴリズムの1つですOslogs)、sサンプルサイズを示します。また、Os)。

0
Pavel Ruzankin