Уникальные (неповторяющиеся) случайные числа в O (1)? - PullRequest
172 голосов
/ 13 октября 2008

Я хотел бы генерировать уникальные случайные числа от 0 до 1000, которые никогда не повторяются (то есть 6 не появляется дважды), но это не прибегает к чему-то вроде поиска O (N) предыдущих значений, чтобы сделать Это. Возможно ли это?

Ответы [ 21 ]

2 голосов
/ 03 января 2009

Вы можете использовать хороший генератор псевдослучайных чисел с 10 битами и отбрасывать 1001–1023, оставляя от 0 до 1000.

Начиная с здесь мы получаем проект для 10-битного PRNG ..

  • 10 бит, полином обратной связи x ^ 10 + x ^ 7 + 1 (период 1023)

  • используйте Galois LFSR для получения быстрого кода

2 голосов
/ 23 мая 2012
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), как требуется.
Примечание: Random должен быть статическим с применением безопасности потока.

2 голосов
/ 17 декабря 2016

Я думаю, что Линейный конгруэнтный генератор будет самым простым решением.

enter image description here

и существует только 3 ограничения для a , c и m значения

  1. m и c относительно простые,
  2. a-1 делится на все простые множители m
  3. a-1 делится на 4 , если m делится 4

PS метод уже упоминался, но пост содержит неверные предположения о постоянных значениях. Константы ниже должны нормально работать для вашего случая

В вашем случае вы можете использовать a = 1002, c = 757, m = 1001

X = (1002 * X + 757) mod 1001
2 голосов
/ 27 апреля 2016

Допустим, вы хотите просматривать перетасованные списки снова и снова, без задержки O(n) каждый раз, когда вы начинаете перетасовывать ее снова, в этом случае мы можем сделать это:

  1. Создание 2 списков A и B, от 0 до 1000, занимает 2n пробел.

  2. Перемешивание списка A с использованием Фишера-Йетса, занимает n время.

  3. При рисовании числа выполните одноэтапное перемешивание Фишера-Йейтса в другом списке.

  4. Когда курсор находится в конце списка, переключитесь на другой список.

Preprocess

cursor = 0

selector = A
other    = B

shuffle(A)

Draw

temp = selector[cursor]

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

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

return temp
1 голос
/ 03 июля 2017

Вопрос Как эффективно сгенерировать список из K неповторяющихся целых чисел от 0 до верхней границы N связан как дубликат, и если вам нужно что-то, что является O (1) на сгенерированный случайное число (без O (n) начальных затрат)) есть простая настройка принятого ответа.

Создайте пустую неупорядоченную карту (пустая упорядоченная карта будет принимать O (log k) на элемент) от целого к целому - вместо использования инициализированного массива. Установите максимум на 1000, если это максимум,

  1. Выберите случайное число r от 0 до макс.
  2. Убедитесь, что оба элемента карты r и max существуют в неупорядоченной карте. Если они не существуют, создайте их со значением, равным их индексу.
  3. Поменяйте местами элементы r и max
  4. Вернуть элемент max и уменьшить max на 1 (если max становится отрицательным вы сделали).
  5. Вернуться к шагу 1.

Единственное отличие по сравнению с использованием инициализированного массива состоит в том, что инициализация элементов откладывается / пропускается, но при этом будут генерироваться точно такие же числа из одного и того же PRNG.

1 голос
/ 22 ноября 2016

Когда N больше 1000, и вам нужно нарисовать K случайных выборок, вы можете использовать набор, который пока содержит выборки. Для каждого розыгрыша вы используете выборку отбраковок , что будет «почти» операцией O (1), поэтому общее время работы при хранении O (N) составляет почти O (K).

Этот алгоритм сталкивается с коллизиями, когда K "около" N. Это означает, что время работы будет намного хуже, чем O (K). Простое решение состоит в том, чтобы изменить логику так, чтобы при K> N / 2 вы вели учет всех выборок, которые еще не были отрисованы. Каждый розыгрыш удаляет образец из набора отклонений.

Другая очевидная проблема с выборкой отклонения состоит в том, что это O (N) хранилище, что является плохой новостью, если N исчисляется миллиардами или более. Тем не менее, есть алгоритм, который решает эту проблему. Этот алгоритм называется алгоритмом Виттера после его изобретателя. Алгоритм описан здесь . Суть алгоритма Виттера заключается в том, что после каждого розыгрыша вы вычисляете случайный пропуск с использованием определенного распределения, которое гарантирует равномерную выборку.

1 голос
/ 02 июня 2015

Большинство ответов здесь не гарантируют, что они не будут возвращать один и тот же номер дважды. Вот правильное решение:

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 других выходов значение может повторяться, но это наивно позволяет 0 следовать сразу после 0, пока они оба появляются в конце и в начале наборов 1000. И наоборот, пока можно сохранять расстояние 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 голос
/ 22 февраля 2015

Вот пример кода 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 голос
/ 13 октября 2008

Другая возможность:

Вы можете использовать массив флагов. И возьмите следующий, когда он уже выбран.

Но будьте осторожны после 1000 вызовов, функция никогда не завершится, поэтому вы должны принять меры предосторожности.

0 голосов
/ 30 октября 2017

Пожалуйста, смотрите мой ответ на https://stackoverflow.com/a/46807110/8794687

Это один из простейших алгоритмов со средней сложностью по времени O ( с log s ), s , обозначающий выборку размер. Там также есть несколько ссылок на алгоритмы хеш-таблиц, сложность которых, как утверждается, составляет O ( s ).

...