私は0から1000の間で繰り返されない(つまり、6が2回表示されない)ユニークな乱数を生成したいのですが、以前の値のO(N)検索のようなものに頼っていません。これは可能ですか?
O(n)
時間やメモリなど)にわたって何かを禁止している場合、受け入れられた回答を含め、以下の回答の多くは間違っています。
私は0から1000の間で繰り返されない(つまり、6が2回表示されない)ユニークな乱数を生成したいのですが、以前の値のO(N)検索のようなものに頼っていません。これは可能ですか?
O(n)
時間やメモリなど)にわたって何かを禁止している場合、受け入れられた回答を含め、以下の回答の多くは間違っています。
回答:
1001整数の配列を0〜1000の値で初期化し、変数maxを配列の現在の最大インデックス(1000から開始)に設定します。0からmaxまでの乱数rを選択し、位置rの数値を位置maxの数値と交換して、位置maxの数値を返します。最大値を1減らして続行します。maxが0の場合、maxを配列のサイズ-1に設定し直して、配列を再初期化する必要なく再起動します。
更新: 質問に答えたとき、私は自分でこの方法を思いつきましたが、いくつかの調査の結果、これがDurstenfeld-Fisher-YatesまたはKnuth-Fisher-Yatesとして知られるフィッシャーイェイツの修正版であることに気付きました。説明を理解するのは少し難しいかもしれないので、以下の例を示します(1001ではなく11要素を使用)。
配列は、11個の要素がarray [n] = nに初期化された状態で始まり、最大値は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にリセットして、プロセスを続行できます。
N
毎回目的の結果を得るために反復(この例では11)を実行する必要があるのではないでしょうO(n)
か?同じ初期状態から組み合わせN
を取得するには反復を行う必要があるのでN!
、そうしないと出力はN状態の1つだけになります。
あなたはこれを行うことができます:
そのため、毎回古い値を検索する必要はありませんが、最初のシャッフルにはO(N)が必要です。しかし、ニルスがコメントで指摘したように、これは償却されたO(1)です。
これは、Cの数行で実装可能であり、実行時にいくつかのテスト/ブランチを実行し、追加とビットシフトを実行します。ランダムではありませんが、ほとんどの人を騙します。
あなたは線形合同ジェネレータを使用することができます。どこm
(モジュラス)は、範囲外の数を取得すると、ちょうど次のいずれかを取得1000より最寄りのプライム大きくなるでしょう。すべての要素が発生したときにのみシーケンスが繰り返され、テーブルを使用する必要はありません。ただし、このジェネレーターの欠点(ランダム性の欠如を含む)に注意してください。
k
、シーケンス内で離れている番号が同時に発生することはありません)。
フォーマットを保持する暗号化を使用できますを、カウンターを暗号化。カウンターは0から上向きになり、暗号化は選択したキーを使用して、任意の基数と幅のランダムに見える値に変換します。たとえば、この質問の例:基数10、幅3。
ブロック暗号は通常、たとえば64ビットまたは128ビットの固定ブロックサイズを持っています。ただし、フォーマットを保持する暗号化を使用すると、AESなどの標準的な暗号を使用し、基数と幅を問わず、暗号学的に堅牢なアルゴリズムを使用して、より狭い幅の暗号を作成できます。
衝突がないことは保証されています(暗号化アルゴリズムが1:1マッピングを作成するため)。また、これは可逆的(双方向マッピング)なので、結果の数値を取得して、最初のカウンター値に戻すことができます。
この手法は、シャッフルされた配列などを格納するためにメモリを必要としません。これは、メモリが限られているシステムでは有利です。
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
... ...
異なる非反復疑似ランダムシーケンスを取得するには、暗号化キーを変更します。各暗号化キーは、異なる非反復疑似ランダムシーケンスを生成します。
k
、シーケンス内で離れている値が同時に発生することはありません)。
k
か?
1,2,...,N
を同じ番号のシーケンスに置き換えることですが、一定の順序のままです。次に、このシーケンスから番号が1つずつ取り出されます。k
選択された値の数です(OPは文字を指定しなかったため、1つ紹介する必要がありました)。
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暗号に触発されています。
hash()
上記のコードで使用されているように、安全な疑似ランダム関数であると仮定すると、この構造は証明可能な疑似ランダム順列を生成します。キースペース全体の検索。これは、キーの長さで指数関数的です。適度なサイズのキー(128ビットなど)の場合でも、これは地球で利用可能な総計算能力を超えています。
hash( key + "/" + int2str(temp) )
上記のアドホック構造をHMACに置き換えることをお勧めします。そのセキュリティは、基礎となるハッシュ圧縮関数のセキュリティに低下する可能性があります。また、HMACを使用すると、安全ではない非暗号ハッシュ関数を使用して、誰かが誤ってこの構造を使用しようとする可能性は低くなります。)
ここで説明されている私のXincrolアルゴリズムを使用できます。
http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html
これは、配列、リスト、順列、または重いCPU負荷のない、ランダムだが一意の数値を生成する純粋なアルゴリズム手法です。
最新バージョンでは、番号の範囲を設定することもできます。たとえば、0〜1073741821の範囲の一意の乱数が必要な場合。
私は実際にそれを使用しました
オープンで無料です。試してみる...
k
、シーケンス内で離れている値が同時に発生することはありません)。
これを解決するために配列さえ必要ありません。
ビットマスクとカウンターが必要です。
カウンターをゼロに初期化し、連続する呼び出しでそれをインクリメントします。カウンターをビットマスク(起動時にランダムに選択、または固定)とXORして、疑似乱数を生成します。1000を超える数を使用できない場合は、9ビットよりも広いビットマスクを使用しないでください。(つまり、ビットマスクは511以下の整数です。)
カウンタが1000を超えたら、カウンタをゼロにリセットしてください。この時点で、別のランダムビットマスクを選択して(必要に応じて)、同じ番号のセットを異なる順序で生成できます。
線形合同ジェネレーターが最も簡単な解決策になると思います。
また、a、c、mの値には3つの制限しかない
PSメソッドは既に言及されましたが、投稿には定数値に関する誤った仮定があります。以下の定数はあなたのケースではうまくいくはずです
あなたのケースでは、あなたが使用することができa = 1002
、c = 757
、m = 1001
X = (1002 * X + 757) mod 1001
最初に入力したコードは、最初のソリューションのロジックを使用しています。私はこれが「言語にとらわれない」ことを知っていますが、誰かが迅速で実用的な解決策を探している場合に備えて、これを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]);
}
この方法は、制限が高く、少数の乱数のみを生成する場合に適しています。
#!/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";
}
番号は昇順で生成されますが、後でシャッフルできることに注意してください。
(top,n)=(100,10)
は次のとおり(0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635)
です。私はPythonでテストしたので、ここで数学のわずかな違いが役割を果たすかもしれません(計算のためのすべての演算r
が浮動小数点であることを確認しました)。
10ビットの優れた疑似乱数ジェネレータを使用して、1001〜1023を破棄し、0〜1000を残すことができます。
ここから、10ビットPRNGのデザインを取得します。
10ビット、フィードバック多項式x ^ 10 + x ^ 7 + 1(期間1023)
ガロアLFSRを使用して高速コードを取得する
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)複雑になります。
注:ランダムは、スレッドセーフが適用された状態で静的でなければなりません。
シャッフルされたリストを何度も何度もやり直したいとしO(n)
ます。最初からやり直すたびに遅延は発生しませんが、その場合は次のようにします。
AとBの2つのリストを作成します(0から1000まで)2n
。
フィッシャーイェイツを使用してリストAをシャッフルするには、時間がかかりますn
。
数字を描くときは、他のリストで1ステップのフィッシャーイェーツのシャッフルを行います。
カーソルがリストの最後にあるとき、他のリストに切り替えます。
前処理
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
[1,3,4,5,2]
すると、シャッフルと同じ結果になり[1,2,3,4,5]
ます。
質問0と上限Nの間のKの非繰り返し整数のリストを効率的に生成するにはどうすればよいですか?スタートアップ費用))受け入れられた答えの簡単な微調整があります。
初期化された配列を使用する代わりに、整数から整数へ、空の順序付けられていないマップを作成します(空の順序付けされたマップは要素ごとにO(log k)を取ります)。それが最大の場合、最大値を1000に設定し、
初期化された配列を使用した場合との唯一の違いは、要素の初期化が延期/スキップされることですが、同じPRNGからまったく同じ数が生成されます。
別の可能性:
フラグの配列を使用できます。それがすでに選択されている場合は、次のものを選択してください。
ただし、1000回の呼び出しの後、関数が終了することはないため、安全策を講じる必要があることに注意してください。
これは、試してみることができるサンプルCOBOLコードです。
私はあなたにRANDGEN.exeファイルを送ることができるので、それを使って遊んで、あなたが望んでいるかどうかを確認することができます。
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.
ここでの回答のほとんどは、同じ数値を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;
}
Nが1000より大きく、K個のランダムサンプルを描画する必要がある場合、これまでのサンプルを含むセットを使用できます。ドローごとに拒否サンプリングを使用します。これは「ほぼ」O(1)操作になるため、合計実行時間はO(N)ストレージでほぼO(K)になります。
このアルゴリズムは、KがNに「近い」ときに衝突します。つまり、実行時間はO(K)よりもはるかに悪くなります。単純な修正は、論理を逆にすることです。これにより、K> N / 2の場合、まだ描画されていないすべてのサンプルの記録を保持できます。ドローごとに、拒否セットからサンプルが削除されます。
拒否サンプリングのもう1つの明白な問題は、それがO(N)ストレージであることです。これは、Nが数十億以上の場合は悪いニュースです。ただし、その問題を解決するアルゴリズムがあります。このアルゴリズムは、発明者からVitterのアルゴリズムと呼ばれています。アルゴリズムはここで説明されています。Vitterのアルゴリズムの要点は、各描画の後に、一定のサンプリングを保証する特定の分布を使用してランダムスキップを計算することです。
for i from n−1 downto 1 do
j ← random integer such that 0 ≤ j ≤ i
exchange a[j] and a[i]
最後の2つのスワップは1つだけ必要なので、実際には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;
}
https://stackoverflow.com/a/46807110/8794687で私の答えを参照してください
これは、平均時間の複雑さO(s log s)を持つ最も単純なアルゴリズムの1つであり、sはサンプルサイズを示します。複雑さがO(s)であると主張されているハッシュテーブルアルゴリズムへのリンクもいくつかあります。
誰かが「Excelで乱数を作成する」と投稿しました。私はこの理想を使用しています。str.indexとstr.ranの2つの部分で構造を作成します。10個の乱数に対して、10個の構造の配列を作成します。str.indexを0から9に設定し、str.ranを別の乱数に設定します。
for(i=0;i<10; ++i) {
arr[i].index = i;
arr[i].ran = rand();
}
配列をarr [i] .ranの値でソートします。str.indexはランダムな順序になりました。以下はCコードです:
#include <stdio.h>
#include <stdlib.h>
struct RanStr { int index; int ran;};
struct RanStr arr[10];
int sort_function(const void *a, const void *b);
int main(int argc, char *argv[])
{
int cnt, i;
//seed(125);
for(i=0;i<10; ++i)
{
arr[i].ran = rand();
arr[i].index = i;
printf("arr[%d] Initial Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
}
qsort( (void *)arr, 10, sizeof(arr[0]), sort_function);
printf("\n===================\n");
for(i=0;i<10; ++i)
{
printf("arr[%d] Random Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
}
return 0;
}
int sort_function(const void *a, const void *b)
{
struct RanStr *a1, *b1;
a1=(struct RanStr *) a;
b1=(struct RanStr *) b;
return( a1->ran - b1->ran );
}