O (1)의 고유 한 (반복되지 않는) 난수?


179

반복하지 않는 0에서 1000 사이의 고유 한 난수를 생성하고 싶습니다 (예 : 6은 두 번 표시되지 않음). 이전 값을 O (N) 검색하는 것과 같은 것은 아닙니다. 이게 가능해?



2
0과 1000 사이의 0입니까?
피트 Kirkham

4
일정 시간이 지남에 따라 ( O(n)시간이나 메모리 와 같은) 무언가를 금지하는 경우 수락 된 답변을 포함하여 아래의 많은 답변이 잘못되었습니다.
jww

한 묶음의 카드를 어떻게 섞을 것인가?
Colonic Panic

9
경고! 실제로 무작위 시퀀스를 생성하지 않기 위해 아래에 주어진 많은 답변 은 O (n)보다 느리거나 그렇지 않으면 결함이 있습니다! codinghorror.com/blog/archives/001015.html 은 당신이 그것들 중 하나를 사용하거나 직접 조작하기 전에 반드시 읽어야합니다!
ivan_pozdeev

답변:


247

값을 0-1000으로 1001 개의 정수 배열을 초기화하고 변수 max를 배열의 현재 최대 인덱스 (1000으로 시작)로 설정하십시오. 0에서 최대 사이의 난수 r을 선택하고, 위치 r에있는 숫자를 최대 위치에있는 숫자로 바꾸고, 이제 최대 위치에있는 숫자를 반환하십시오. 최대 값을 1 씩 줄이고 계속하십시오. max가 0 인 경우 max를 배열의 크기-1로 다시 설정하고 배열을 다시 초기화 할 필요없이 다시 시작하십시오.

업데이트 : 질문에 대답했을 때이 방법을 스스로 생각해 냈지만 일부 연구 후에 이것이 Durstenfeld-Fisher-Yates 또는 Knuth-Fisher-Yates로 알려진 수정 된 버전의 Fisher-Yates 임을 알았습니다. 설명하기가 약간 어려울 수 있으므로 아래 예제를 제공했습니다 (1001 대신 11 요소 사용).

11 개의 요소가 array [n] = n으로 초기화 된 상태에서 배열이 시작되고 최대 값은 10에서 시작합니다.

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

각 반복에서 임의의 숫자 r이 0과 최대 사이에서 선택되고 array [r]과 array [max]가 바뀌고 새 배열 [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|
+--+--+--+--+--+--+--+--+--+--+--+

이 시점에서 최대 값을 10으로 재설정하고 프로세스를 계속할 수 있습니다.


6
셔플에 제프의 게시물이 ... 좋은 난수를 반환하지 않습니다 제안 codinghorror.com/blog/archives/001015.html
프로

14
@Peter Rounce : 생각하지 않습니다. 이것은 Fisher Yates 알고리즘처럼 보입니다 .Jeff의 게시물에도 인용되었습니다 (좋은 사람으로).
Brent. Longborough

3
@ robert : 질문의 이름 에서처럼 "O (1)의 고유 한 난수"라는 단어가 나오지 않는다고 지적하고 싶었습니다.
찰스

3
@mikera : 동의하지만 기술적으로 고정 크기 정수를 사용하는 경우 전체 목록을 O (1) (큰 상수, 즉 2 ^ 32)로 생성 할 수 있습니다. 또한 실제 목적을 위해 "무작위"의 정의가 중요합니다. 실제로 시스템의 엔트로피 풀을 사용하려는 경우 제한은 계산 자체가 아닌 임의 비트의 계산이며,이 경우 n log n은 관련이 있습니다. 다시. 그러나 / dev / random 대신 / dev / urandom을 사용하는 경우에는 '실제로'O (n)으로 돌아갑니다.
Charles

4
조금 혼란 스럽습니다. N매번 원하는 결과를 얻기 위해 반복 (이 예제에서는 11) 을 수행해야한다는 사실이 아닙니다 O(n). 동일한 초기 상태에서 조합 N을 얻으려면 반복 을 수행해야하므로 N!그렇지 않으면 출력은 N 상태 중 하나입니다.
Seph

71

당신은 이것을 할 수 있습니다 :

  1. 목록 0..1000을 만듭니다.
  2. 목록을 섞으십시오. 이를 수행하는 좋은 방법 은 Fisher-Yates shuffle 을 참조하십시오 .
  3. 셔플 된 목록에서 순서대로 숫자를 반환하십시오.

따라서 매번 이전 값을 검색 할 필요는 없지만 초기 셔플에는 여전히 O (N)이 필요합니다. 그러나 Nils가 의견에서 지적했듯이, 이것은 O (1)로 상각됩니다.


5
@Just Some Guy N = 1000, 당신은 그것이 O (1) 인 O (N / N)라고 말합니다
Guvante

1
셔플 배열에 대한 각 삽입이 연산 인 경우 1 값을 삽입 한 후 1의 임의 값을 얻을 수 있습니다. 2 값의 경우 2, n 값의 경우 n. 리스트를 생성하려면 n 개의 연산이 필요하므로 전체 알고리즘은 O (n)입니다. 1,000,000 개의 임의 값이 필요한 경우 1,000,000 개의 작업이 필요합니다
Kibbee

3
이 방법을 생각해보십시오. 일정한 시간이라면 10 개의 난수에 대해 100 억의 시간과 동일한 시간이 걸립니다. 그러나 셔플 링으로 O (n)을 복용했기 때문에 이것이 사실이 아니라는 것을 알고 있습니다.
Kibbee

1
n lg n 임의의 비트를 생성해야하므로 실제로 상각 시간 O (log n)가 필요합니다.
Charles

2
그리고 지금, 나는 그것을 할 모든 정당성을 가지고 있습니다! meta.stackoverflow.com/q/252503/13
Chris Jester-Young

60

최대 선형 피드백 시프트 레지스터를 사용하십시오 .

그것은 몇 줄의 C로 구현 가능하며 런타임에는 몇 가지 테스트 / 분기, 약간의 추가 및 비트 이동 이상을 수행하지 않습니다. 그것은 무작위가 아니지만 대부분의 사람들을 바보입니다.


12
"그것은 무작위가 아니지만 대부분의 사람들을 속인다". 이는 모든 의사 난수 생성기와이 질문에 대한 모든 가능한 답변에 적용됩니다. 그러나 대부분의 사람들은 그것에 대해 생각하지 않습니다. 따라서이 메모를 생략하면 더 많은지지를받을 수 있습니다.
f3lix

3
@bobobobo : O (1) 메모리가 이유입니다.
Ash

3
Nit : O (log N) 메모리입니다.
Paul Hankin

2
이 방법을 사용하여 숫자를 생성하는 방법은 0에서 800000 사이입니다. 일부는 기간이 1048575 (2 ^ 20-1) 인 LFSR을 사용하고 숫자가 범위를 벗어나면 다음 항목을 얻을 수 있지만 효율적이지 않습니다.
tigrou

1
LFSR은 균일하게 분포 된 시퀀스를 생성하지 않습니다. 생성 될 전체 시퀀스는 첫 번째 요소에 의해 정의됩니다.
ivan_pozdeev

21

선형 일치 생성기를 사용할 수 있습니다 . 어디 m당신이 범위를 벗어난 숫자를 얻을 때 (계수)가 1000보다 가까운 주요 더 큰 것, 바로 다음 하나를 얻을. 시퀀스는 모든 요소가 발생한 후에 만 ​​반복되며 테이블을 사용할 필요가 없습니다. 그러나이 생성기의 단점 (임의의 부족 포함)을 알고 있어야합니다.


1
1009는 1000 이후 첫 번째 소수입니다.
Teepeemm

LCG는 연속적인 숫자들 사이에 높은 상관 관계를 가지므로, 조합 은 크게 랜덤하지 않을 것이다 (예를 들어 k, 시퀀스에서 떨어져있는 숫자 는 결코 함께 일어날 수 없다).
ivan_pozdeev

m은 요소 1001 (0의 경우 1000 + 1)의 수 여야하며 다음 = (1002 * Current + 757) mod 1001을 사용할 수 있습니다.
Max Abramovich

21

형식 보존 암호화를 사용할 수 있습니다 카운터를 암호화 할 수 있습니다. 카운터는 0에서 위로 올라가고 암호화는 원하는 키를 사용하여 원하는 기수와 너비의 임의의 값으로 바꿉니다. 예를 들어이 질문의 예는 기수 10, 너비 3입니다.

블록 암호는 일반적으로 고정 블록 크기 (예 : 64 또는 128 비트)를 갖습니다. 그러나 Format-Preserving Encryption을 사용하면 AES와 같은 표준 암호를 사용하고 원하는 암호화 기수를 사용하는 알고리즘을 사용하여 원하는 기수와 너비의 더 작은 너비의 암호를 만들 수 있습니다.

암호화 알고리즘이 1 : 1 매핑을 생성하기 때문에 충돌이 발생하지 않습니다. 또한 뒤집을 수 있으므로 (양방향 매핑) 결과 숫자를 가져 와서 시작한 카운터 값으로 되돌릴 수 있습니다.

이 기술은 셔플 배열 등을 저장하는 데 메모리가 필요하지 않으므로 메모리가 제한된 시스템에서 유리할 수 있습니다.

AES-FFX 는이를 달성하기 위해 제안 된 표준 방법 중 하나입니다. 완벽하게 준수하지는 않지만 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
...   ...

반복되지 않는 다른 의사 난수 시퀀스를 얻으려면 암호화 키를 변경하십시오. 각 암호화 키는 서로 다른 반복되지 않는 의사 랜덤 시퀀스를 생성합니다.


이것은 본질적으로 간단한 매핑이므로 LCG 및 LFSR과 전혀 다르지 않으며 모든 관련 꼬임이 있습니다 (예 : k시퀀스에서 떨어져있는 값 은 절대로 함께 발생할 수 없음).
ivan_pozdeev

@ivan_pozdeev : 귀하의 의견의 의미를 이해하는 데 어려움이 있습니다. 이 매핑의 문제점, "모든 관련 꼬임"및 문제점이 무엇인지 설명 할 수 있습니까 k?
Craig McQueen

여기서 효과적으로 수행되는 모든 "암호화"는 시퀀스 1,2,...,N를 다른, 그러나 여전히 일정한 순서로 동일한 숫자 의 시퀀스 로 대체합니다 . 그런 다음이 순서에서 숫자를 하나씩 가져옵니다. k선택한 값의 수입니다 (OP는 문자를 지정하지 않았으므로 소개해야했습니다).
ivan_pozdeev

3
@ivan_pozdeev FPE가 특정 정적 매핑을 구현해야하거나 "반환 ​​된 조합이 첫 번째 숫자로 완전히 정의 된"경우는 아닙니다. 구성 매개 변수는 첫 번째 숫자의 크기 (천 개만있는 상태)보다 훨씬 크기 때문에 동일한 초기 값으로 시작한 다음 다른 후속 값으로 진행하는 여러 시퀀스가 ​​있어야합니다. 현실적인 생성기는 순열의 가능한 전체 공간을 덮지 못합니다. OP가 요청하지 않은 경우 실패 모드를 올리는 것은 가치가 없습니다.
sh1

4
+1. 무작위로 균일하게 선택된 키를 갖는 보안 블록 암호를 사용하여 올바르게 구현되면,이 방법을 사용하여 생성 된 시퀀스는 실제 랜덤 셔플과 계산 상 구별 할 수 없습니다. 다시 말해, 가능한 모든 블록 암호 키를 테스트하고 그 중 하나가 동일한 출력을 생성하는지 확인하는 것보다이 방법의 출력을 실제 랜덤 셔플과 크게 빠르게 구별 할 수있는 방법이 없습니다. 128 비트 키 공간을 가진 암호의 경우 이는 아마도 현재 인류가 이용할 수있는 컴퓨팅 능력을 넘어선 것입니다. 256 비트 키를 사용하면 아마도 영원히 남아있을 것입니다.
Ilmari Karonen

7

0 ... 1000과 같은 낮은 숫자의 경우 모든 숫자가 포함 된 목록을 작성하고 셔플하는 것이 간단합니다. 그러나 숫자 집합이 매우 큰 경우 또 다른 우아한 방법이 있습니다. 키와 암호화 해시 함수를 사용하여 의사 난수 순열을 만들 수 있습니다. 다음 C ++-ish 예제 의사 코드를 참조하십시오.

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 암호에서 영감을 얻었습니다 .


stackoverflow.com/a/16097246/648265 와 동일하지만 시퀀스에 대한 무작위성에 실패합니다.
ivan_pozdeev

1
@ivan_pozdeev : 이론 상으로는 무한한 컴퓨팅 능력을 가정합니다. 그러나, 가정 hash(), 위 코드에서 사용되는, 보안 의사 랜덤 함수는,라도 유용 (의 참고 및 Rackoff 1988)를 산출 할 것이다 이러한 구성 의사 랜덤 순열을 철저한보다 훨씬 적은 노력을 이용하여 진정한 랜덤 셔플 구별 할 수없는, 키 길이에 기하 급수적 인 전체 키 공간 검색 합리적인 크기의 키 (예 : 128 비트)의 경우에도 지구상에서 사용할 수있는 총 컴퓨팅 성능을 뛰어 넘습니다.
Ilmari Karonen

(BTW,이 주장을 좀 더 엄격하게하기 위해 hash( key + "/" + int2str(temp) )위의 임시 구성을 HMAC 로 바꾸는 것을 선호 합니다. 그것은 덜 실수로 사람을 안전하지 않은 비 암호화 해시 기능이 구성을 사용하려고).
Ilmari 카로 넨에게

6

여기에 설명 된 내 Xincrol 알고리즘을 사용할 수 있습니다.

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

이것은 배열, 목록, 순열 또는 과도한 CPU로드없이 임의의 고유 한 숫자를 생성하는 순수한 알고리즘 방법입니다.

최신 버전에서는 숫자 범위를 설정할 수도 있습니다 (예 : 0-1073741821 범위의 고유 한 난수를 원하는 경우).

나는 실제로 그것을 사용했습니다

  • 모든 노래를 무작위로 재생하지만 앨범 / 디렉토리 당 한 번만 재생되는 MP3 플레이어
  • 픽셀 단위의 비디오 프레임 용해 효과 (빠르고 부드럽게)
  • 서명 및 마커에 대한 이미지 위에 비밀 "노이즈"안개 만들기 (스테 가노 그래피)
  • 데이터베이스를 통한 대량의 Java 객체 직렬화를위한 데이터 객체 ID
  • 트리플 과반수 메모리 비트 보호
  • 주소 + 값 암호화 (모든 바이트는 암호화 될뿐만 아니라 버퍼의 새로운 암호화 된 위치로 이동) 이것은 실제로 cryptanalysis 동료를 화나게했습니다 :-)
  • 일반 텍스트에서 일반 텍스트로 SMS, 이메일 등을위한 암호화 텍스트 암호화
  • 내 텍사스 홀덤 포커 계산기 (THC)
  • 시뮬레이션을위한 몇 가지 게임, "셔플 링", 순위

무료입니다. 시도 해봐...


이 방법이 10 진수 값에 대해 작동 할 수 있습니까 (예 : 3 자리 10 진수 카운터를 스크램블하여 항상 3 자리 10 진수 결과를 갖도록 할 수 있습니까?
Craig McQueen

Xorshift 알고리즘 의 예로 , 모든 관련 꼬임이 포함 된 LFSR입니다 (예 : k시퀀스에서 떨어져있는 값 은 절대로 함께 발생할 수 없음).
ivan_pozdeev

5

이 문제를 해결하기 위해 배열이 필요하지 않습니다.

비트 마스크와 카운터가 필요합니다.

카운터를 0으로 초기화하고 연속 호출에서 증가시킵니다. 의사 난수를 생성하기 위해 비트 마스크 (시작시 무작위로 선택되거나 고정 된)와 카운터를 XOR합니다. 1000을 초과하는 숫자를 가질 수 없으면 9 비트보다 넓은 비트 마스크를 사용하지 마십시오. 즉, 비트 마스크는 511보다 크지 않은 정수입니다.

카운터가 1000을 통과하면 카운터를 0으로 재설정해야합니다. 이때 다른 랜덤 비트 마스크 (원하는 경우)를 선택하여 다른 순서로 동일한 숫자 세트를 생성 할 수 있습니다.


2
그것은 LFSR보다 더 적은 사람들을 속일 것입니다.
starblue

512 ... 1023 내의 "비트 마스크"도 괜찮습니다. 좀 더 가짜 무작위로 내 대답을 참조하십시오. :-)
sellibitze 2016 년

stackoverflow.com/a/16097246/648265 와 본질적으로 동일 하며 시퀀스의 무작위성도 실패합니다.
ivan_pozdeev

4

선형 합동 발생기 가 가장 간단한 해결책 이라고 생각합니다 .

여기에 이미지 설명을 입력하십시오

a , cm에는 3 가지 제한 사항 만 있습니다

  1. m c 는 비교적 소수입니다.
  2. a-1 m의 모든 주요 요소로 나눌 수 있습니다
  3. m 4 로 나눌 수있으면 a-1 4 로 나눌 수 있습니다

추신 : 이 방법은 이미 언급되었지만 게시물에는 상수 값에 대한 잘못된 가정이 있습니다. 아래 상수는 귀하의 경우에 잘 작동합니다.

귀하의 경우에는 당신이 사용할 수 a = 1002, c = 757,m = 1001

X = (1002 * X + 757) mod 1001

3

다음은 첫 번째 솔루션의 논리를 사용하여 입력 한 코드입니다. 나는 이것이 "언어에 구애받지 않는다"는 것을 알고 있지만 누군가가 빠른 실용적인 솔루션을 찾고있는 경우 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

이 방법은 한계가 높고 소수의 난수 만 생성하려고 할 때 적절 합니다.

#!/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";
}

숫자는 오름차순으로 생성되지만 나중에 셔플 할 수 있습니다.


이 조합이 아닌 순열을 생성하기 때문에, 더 적합의 stackoverflow.com/questions/2394246/...
ivan_pozdeev

1
테스트 프로그램이 낮은 수치를 향해 바이어스를 가지고와 2M 샘플에 대한 측정 가능성이 (top,n)=(100,10)있습니다 (0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635). 파이썬으로 테스트 했으므로 수학의 약간의 차이가 여기에서 중요한 역할을 할 수 있습니다 (계산 r을 위한 모든 연산 이 부동 소수점 인지 확인했습니다 ).
ivan_pozdeev

예,이 방법이 올바르게 작동하려면 상한이 추출 할 값의 수보다 훨씬 커야합니다.
살바

"상한값이 값의 수보다 훨씬 큰" 경우에도 "올바르게"작동하지 않습니다 . 확률은 여전히 ​​더 적은 마진으로 불균일합니다.
ivan_pozdeev '16

2

좋은 의사 난수 생성기를 사용할 수 있습니다. (10 개) 비트를 1000에 0을 떠나 1023 1001 버린다.

에서 여기에 우리는 10 비트 PRNG에 대한 설계를 얻을 수 ..

  • 10 비트, 피드백 다항식 x ^ 10 + x ^ 7 + 1 (기간 1023)

  • Galois LFSR을 사용하여 빠른 코드 얻기


선형 피드백 시프트 레지스터를 기반으로하는 10 비트 PRNG는 일반적으로 첫 번째 값으로 돌아 가기 전에 모든 값 (1 제외)을 한 번만 가정하는 구조로 만들어지기 때문에 발생하지 않습니다. 다시 말해, 사이클 동안 정확히 한 번만 1001을 선택합니다.
Nuoji

1
@이 질문의 요점은 각 숫자를 정확히 한 번만 선택하는 것입니다. 그리고 1001이 두 번 연속 발생하지 않는다고 불평합니까? 최적의 스프레드를 가진 LFSR은 의사 랜덤 방식으로 공간의 모든 숫자를 통과 한 다음 사이클을 다시 시작합니다. 즉, 일반적인 임의 함수로 사용되지 않습니다. 랜덤으로 사용될 때, 우리는 일반적으로 비트의 서브셋만을 사용합니다. 그것에 대해 조금 읽으면 곧 이해가 될 것입니다.
Nuoji

1
유일한 문제는 주어진 LFSR이 하나의 시퀀스 만 가지고 있기 때문에, 특히 모든 가능한 조합을 생성하지는 않고 선택된 숫자들 사이에 강한 상관 관계를 제공한다는 것입니다.
ivan_pozdeev

2
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 ^ 2)입니다.
ivan_pozdeev

min = 0 max = 10000000 및 N = 5를 선택하면 선택한 수에 관계없이 ~ = 0을 재 시도합니다. 그러나 그렇습니다. 최대 최소값이 작 으면 o (N)이 깨지는 지점이 있습니다.
Erez Robinson

N << (max-min)이면 여전히 비례하고 계수가 매우 작습니다. 그리고 계수는 점근 적 추정에 중요하지 않습니다.
ivan_pozdeev

이것은 O (n)이 아닙니다. 세트에 값이 포함될 때마다 이것은 추가 루프입니다.
paparazzo

2

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 개의 목록을 유지 하거나 목록을 소진 할 필요는 없습니다 . Fisher-Yates는 초기 상태에서 균일하게 임의의 결과를 제공합니다. 설명 은 stackoverflow.com/a/158742/648265 를 참조하십시오 .
ivan_pozdeev

@ivan_pozdeev 예, 같은 결과입니다. 그러나 여기서 제 생각은 셔플 드로잉 작업의 일부를 만들어서 O (1)를 상각하게하는 것입니다.
Khaled.K

당신은 이해하지 못했습니다. 당신 전혀 목록을 재설정 할 필요가 없습니다 다시 셔플 전에. 셔플 링 [1,3,4,5,2]은 셔플 링 과 동일한 결과를 생성합니다 [1,2,3,4,5].
ivan_pozdeev

2

질문 0에서 상한 N 사이의 K 반복되지 않는 정수 목록을 효율적으로 생성하는 방법 중복으로 연결되어 있습니다 - 당신은 어떤 O (N에 생성 된 난수 당 O (1) 뭔가를 (원하는 경우) 시작 비용)) 허용되는 답변의 간단한 조정이 있습니다.

초기화 된 배열을 사용하는 대신 정수에서 정수로 빈 정렬되지 않은 맵 (빈 정렬 맵은 요소 당 O (log k)을 사용함)을 만듭니다. 최대 값 인 경우 최대 값을 1000으로 설정하십시오.

  1. 0에서 최대 사이의 난수 r을 선택하십시오.
  2. 정렬되지 않은 맵에 맵 요소 r과 max가 모두 있는지 확인하십시오. 존재하지 않는 경우 색인과 동일한 값으로 작성하십시오.
  3. 요소 r과 최대 스왑
  4. 요소 최대 값을 반환하고 최대 값을 1 씩 줄입니다 (최대 값이 음수이면 완료).
  5. 1 단계로 돌아갑니다.

초기화 된 배열을 사용하는 것과 비교할 때의 유일한 차이점은 요소의 초기화가 연기되거나 건너 뛴다는 것입니다. 그러나 동일한 PRNG에서 동일한 숫자를 생성합니다.


1

또 다른 가능성 :

플래그 배열을 사용할 수 있습니다. 그리고 이미 선택된 경우 다음을 가져 가십시오.

그러나 1000 번의 호출 후에는 기능이 종료되지 않으므로주의해야합니다.


이것은 O (k ^ 2)이며, 지금까지 선택된 값의 수에 평균적으로 비례하는 많은 추가 단계가 있습니다.
ivan_pozdeev

1

다음은 재생할 수있는 샘플 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.

1
이것이 실제로 영업 요구 사항을 충족시킬 수 있는지는 모르겠지만 COBOL 기여를위한 소품입니다!
Mac

1

여기에있는 대부분의 답변은 동일한 숫자를 두 번 반환하지 않을 것이라고 보장하지 않습니다. 올바른 해결책은 다음과 같습니다.

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;
}

이것은 stackoverflow.com/a/196164/648265 와 같은 LCG이며 시퀀스 및 임의의 다른 관련 꼬임에 대해서는 무작위가 아닙니다.
ivan_pozdeev

@ivan_pozdeev는 LCG보다 1001 번째 통화에서 중복을 반환하지 않기 때문에 LCG보다 낫습니다.
sh1

1

N이 1000보다 크고 K 개의 랜덤 샘플을 추출해야하는 경우 지금까지 샘플이 포함 된 세트를 사용할 수 있습니다. 각 추첨에 대해 거부 샘플링 을 사용합니다 대해 "거의"O (1) 연산이 될 하므로 총 실행 시간은 O (N) 저장시 거의 O (K)입니다.

이 알고리즘은 K가 "가까운"N 일 때 충돌이 발생합니다. 이는 실행 시간이 O (K)보다 훨씬 나쁘다는 것을 의미합니다. 간단한 수정은 논리를 반대로하여 K> N / 2의 경우 아직 그려지지 않은 모든 샘플의 기록을 유지하는 것입니다. 각각의 드로우는 거부 세트에서 샘플을 제거합니다.

거부 샘플링의 또 다른 명백한 문제는 O (N) 스토리지라는 점입니다. N이 수십억 이상인 경우 나쁜 소식입니다. 그러나이 문제를 해결하는 알고리즘이 있습니다. 이 알고리즘은 발명자 이후 Vitter의 알고리즘이라고합니다. 알고리즘은 여기 에 설명되어 있습니다 . Vitter 알고리즘의 요점은 각 드로우 후에 균일 한 샘플링을 보장하는 특정 분포를 사용하여 랜덤 스킵을 계산한다는 것입니다.


여러분, 제발! Fisher-Yates 방법이 깨졌습니다. 확률 1 / N 인 첫 번째 항목과 확률 1 / (N-1)! = 1 / N 인 두 번째 항목을 선택합니다. 이것은 바이어스 샘플링 방법입니다! 바이어스를 해결하려면 Vittter의 알고리즘이 필요합니다.
Emanuel Landeholm

0

피셔 예이츠

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)
입니다.

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;
}

이미 이것에 대한 답변이 있지만 그것은 꽤 오래 구부러져 있으며 1에서 멈추지 않는다는 것을 인식하지 못합니다 (0 아님)
paparazzo


-1

누군가 "엑셀에서 난수 생성"을 게시했습니다. 나는이 이상적인 것을 사용하고 있습니다. str.index와 str.ran의 두 부분으로 구성된 구조를 만듭니다. 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 );
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.