Я хотел бы сгенерировать уникальные случайные числа от 0 до 1000, которые никогда не повторяются (т.е. 6 не отображается дважды), но для этого не прибегает к чему-то вроде поиска O (N) предыдущих значений. Это возможно?
Уникальные (неповторяющиеся) случайные числа в O (1)?
Ответы (22)
Инициализируйте массив из 1001 целого числа со значениями 0–1000 и установите для переменной max текущий максимальный индекс массива (начиная с 1000). Выберите случайное число r между 0 и max, поменяйте местами число в позиции r на число в позиции max и верните число теперь в позиции max. Уменьшите max на 1 и продолжайте. Когда max равен 0, установите max обратно на размер массива - 1 и начните снова без необходимости повторно инициализировать массив.
Обновление: хотя я сам придумал этот метод, когда ответил на вопрос, после некоторого исследования я понял, что это модифицированная версия Fisher-Yates, известный как Durstenfeld-Fisher-Yates или Knuth-Fisher-Yates. Поскольку описание может быть немного трудным для понимания, я привел пример ниже (с использованием 11 элементов вместо 1001):
Массив начинается с 11 элементов, инициализированных как array [n] = n, max начинается с 10:
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
+--+--+--+--+--+--+--+--+--+--+--+
^
max
На каждой итерации выбирается случайное число r между 0 и max, 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|
+--+--+--+--+--+--+--+--+--+--+--+
На этом этапе max можно сбросить до 10, и процесс может продолжаться.
N итераций (11 в этом примере), чтобы каждый раз получать желаемый результат, не означает, что это O(n)? Поскольку вам нужно сделать N итераций, чтобы получить N! комбинаций из одного и того же начального состояния, иначе ваш результат будет только одним из N состояний.
- person Seph; 04.12.2011
Ты можешь сделать это:
- Создайте список 0..1000.
- Перемешайте список. (См. перемешивание Фишера-Ятса, чтобы узнать, как это сделать.)
- Вернуть номера по порядку из перетасованного списка.
Таким образом, для этого не требуется каждый раз поиск старых значений, но для начального перемешивания по-прежнему требуется O (N). Но, как указал Нильс в комментариях, это амортизируется O (1).
Используйте регистр сдвига максимальной линейной обратной связи.
Это реализуемо в нескольких строках C и во время выполнения делает немного больше, чем пара тестов / веток, небольшое добавление и битовый сдвиг. Это не случайно, но большинство людей вводит в заблуждение.
Вы можете использовать линейный конгруэнтный генератор. Где m (модуль) будет ближайшим простым числом больше 1000. Когда вы получите число за пределами диапазона, просто получите следующее. Последовательность будет повторяться только после того, как все элементы будут выполнены, и вам не нужно использовать таблицу. Однако помните о недостатках этого генератора (включая отсутствие случайности).
k в последовательности, никогда не могут встречаться вместе).
- person ivan_pozdeev; 08.09.2016
Вы можете использовать шифрование с сохранением формата для шифрования счетчика. Ваш счетчик просто идет от 0 вверх, и шифрование использует ключ по вашему выбору, чтобы превратить его в, казалось бы, случайное значение любой системы счисления и ширины, которую вы хотите. Например. для примера в этом вопросе: основание системы счисления 10, ширина 3.
Блочные шифры обычно имеют фиксированный размер блока, например 64 или 128 бит. Но шифрование с сохранением формата позволяет вам взять стандартный шифр, такой как AES, и создать шифр меньшей ширины, с любым основанием и шириной, какой вы хотите, с алгоритмом, который все еще является криптографически надежным.
Гарантируется, что никогда не будет коллизий (поскольку криптографические алгоритмы создают отображение 1: 1). Это также обратимо (двустороннее отображение), поэтому вы можете взять полученное число и вернуться к начальному значению счетчика.
Этот метод не требует памяти для хранения перетасованного массива и т. Д., Что может быть преимуществом в системах с ограниченной памятью.
AES-FFX - это один предлагаемый стандартный метод для достижения этого. Я экспериментировал с некоторым базовым кодом Python, который основан на идее AES-FFX, хотя и не полностью соответствует - см. здесь код Python. Это может, например, зашифровать счетчик до случайного 7-значного десятичного числа или 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 в последовательности, никогда не могут встречаться вместе).
- person ivan_pozdeev; 08.09.2016
k?
- person Craig McQueen; 08.09.2016
1,2,...,N последовательностью тех же чисел в каком-то другом, но все еще постоянном порядке. Затем числа извлекаются из этой последовательности один за другим. k - это количество выбранных значений (OP не указывал для него букву, поэтому мне пришлось ввести ее).
- person ivan_pozdeev; 08.09.2016
k отдельно, вы просто выбираете подходящую систему счисления r и ширину w в соответствии с вашими потребностями, чтобы получить k = rʷ, а затем генерируете столько n значений, сколько хотите, где n ≤ k. Например, в своем ответе я показал, как получить 16 неповторяющихся чисел в диапазоне 0..999.
- person Craig McQueen; 09.09.2016
N! возможных значений, чтобы охватить все возможные последовательности) в дополнение ко всей дополнительной работе.
- person ivan_pozdeev; 10.09.2016
Для небольших чисел, таких как 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, обратим. Это навеяно шифром Фейстеля.
hash(), используемый в приведенном выше коде, является безопасной псевдослучайной функцией, эта конструкция доказуемо (Luby & Rackoff, 1988) даст псевдослучайная перестановка, которую нельзя отличить от истинной случайной перестановки, требующей значительно меньших усилий, чем исчерпывающий поиск всего ключевого пространства, длина которого экспоненциальна. Даже для ключей разумного размера (скажем, 128 бит) это превышает общую вычислительную мощность, доступную на Земле.
- person Ilmari Karonen; 12.09.2016
hash( key + "/" + int2str(temp) ) выше на HMAC, безопасность которого, в свою очередь, может быть доказуемо снижена до уровня базовой функции сжатия хэша. Кроме того, использование HMAC может снизить вероятность того, что кто-то по ошибке попытается использовать эту конструкцию с небезопасным небезопасным крипто-хеш-функция.)
- person Ilmari Karonen; 12.09.2016
Вы можете использовать мой алгоритм Xincrol, описанный здесь:
http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html
Это чистый алгоритмический метод генерации случайных, но уникальных чисел без массивов, списков, перестановок или большой нагрузки на процессор.
Последняя версия позволяет также установить диапазон чисел, например, если мне нужны уникальные случайные числа в диапазоне 0-1073741821.
Я практически использовал это для
- MP3-плеер, который воспроизводит каждую песню в случайном порядке, но только один раз для каждого альбома / каталога
- Эффект растворения кадров видео по пикселям (быстрый и плавный)
- Создание секретного «шумового» тумана над изображением для подписей и маркеров (стеганография)
- Идентификаторы объектов данных для сериализации огромного количества объектов Java через базы данных
- Защита битов памяти с тройным большинством
- Шифрование адреса + значения (каждый байт не только зашифровывается, но и перемещается в новое зашифрованное место в буфере). Это действительно рассердило на меня криптоаналитиков :-)
- Простой текст в обычный текст, как шифрование текста для SMS, электронных писем и т. Д.
- Мой покерный калькулятор техасского холдема (THC)
- Несколько моих игр для симуляторов, "перетасовок", рейтинговых
- более
Это открыто, бесплатно. Попробуйте ...
k по отдельности в последовательности никогда не могут встречаться вместе).
- person ivan_pozdeev; 08.09.2016
Я думаю, что Линейный конгруэнтный генератор был бы самым простым решением.
и есть только 3 ограничения на a, c и m em > значения
- m и c являются относительно простыми,
- a-1 делится на все простые множители m.
- a-1 делится на 4, если m strong > делится на 4
PS метод уже упоминался, но в сообщении неверные предположения о постоянных значениях. Приведенные ниже константы должны работать в вашем случае.
В вашем случае вы можете использовать a = 1002, c = 757, m = 1001
X = (1002 * X + 757) mod 1001
Вам даже не нужен массив, чтобы решить эту проблему.
Вам нужна битовая маска и счетчик.
Установите счетчик на ноль и увеличивайте его при последующих вызовах. Выполните XOR счетчика с битовой маской (выбираемой случайным образом при запуске или фиксированной) для генерации псевдослучайного числа. Если у вас не может быть чисел, превышающих 1000, не используйте битовую маску шире 9 бит. (Другими словами, битовая маска - это целое число не выше 511.)
Убедитесь, что когда счетчик достигнет 1000, вы сбросите его на ноль. В это время вы можете выбрать другую случайную битовую маску, если хотите получить тот же набор чисел в другом порядке.
Вот набранный мной код, использующий логику первого решения. Я знаю, что это «агностик языка», но просто хотел представить это в качестве примера на 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 выполняются с плавающей запятой).
- person ivan_pozdeev; 11.09.2016
Вы можете использовать хороший генератор псевдослучайных чисел с 10 битами и выбросить от 1001 до 1023, оставив От 0 до 1000.
Из здесь мы получаем дизайн для 10-битного ГПСЧ ..
10 бит, полином обратной связи x ^ 10 + x ^ 7 + 1 (период 1023)
используйте Galois 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), если требуется.
Примечание. Случайное число должно быть статическим с применением потоковой безопасности.
Вот пример кода 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.
Предположим, вы хотите повторять перемешанные списки снова и снова, без задержки O(n) каждый раз, когда вы начинаете перемешивать их снова, в этом случае мы можем сделать это:
Создание 2 списков A и B от 0 до 1000 занимает
2nпробела.Перемешивание списка A с использованием Fisher-Yates занимает
nвремени.При рисовании числа проделайте одностадийную перетасовку Фишера-Йейтса для другого списка.
Когда курсор находится в конце списка, переключитесь на другой список.
Предварительная обработка
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].
- person ivan_pozdeev; 08.09.2016
Вопрос Как эффективно сгенерировать список из K неповторяющихся целых чисел от 0 до верхней границы N связан как дубликат - и если вам нужно что-то, что составляет O (1) на сгенерированный случайный number (без начальных затрат O (n))) есть простая настройка принятого ответа.
Создайте пустую неупорядоченную карту (пустая упорядоченная карта будет принимать O (log k) на элемент) от целого до целого числа - вместо использования инициализированного массива. Установите max на 1000, если это максимум,
- Выберите случайное число r от 0 до макс.
- Убедитесь, что оба элемента карты r и max существуют в неупорядоченной карте. Если они не существуют, создайте для них значение, равное их индексу.
- Поменять местами элементы r и max
- Вернуть max элемента и уменьшить max на 1 (если max становится отрицательным, все готово).
- Вернуться к шагу 1.
Единственная разница по сравнению с использованием инициализированного массива заключается в том, что инициализация элементов откладывается / пропускается, но при этом будут генерироваться точно такие же числа из того же ГПСЧ.
Еще одна возможность:
Вы можете использовать массив флагов. И возьмите следующий, когда он уже выбран.
Но будьте осторожны после 1000 вызовов, функция никогда не завершится, поэтому вы должны принять меры предосторожности.
Большинство ответов здесь не гарантируют, что они не вернут одно и то же число дважды. Вот правильное решение:
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;
}
Когда N больше 1000 и вам нужно нарисовать K случайных выборок, вы можете использовать набор, который пока содержит образцы. Для каждого розыгрыша вы используете выборку отклонения, что будет «почти» операцией O (1). , поэтому общее время работы составляет около O (K) при хранении O (N).
Этот алгоритм сталкивается с коллизиями, когда K "близко" N. Это означает, что время работы будет намного хуже, чем O (K). Простое исправление - изменить логику так, чтобы при K> N / 2 вы регистрировали все образцы, которые еще не были нарисованы. Каждый розыгрыш удаляет образец из набора для отбраковки.
Другая очевидная проблема с выборкой отбраковки заключается в том, что это O (N) хранилище, что является плохой новостью, если N исчисляется миллиардами или более. Однако есть алгоритм, решающий эту проблему. Этот алгоритм получил название алгоритма Виттера по имени его изобретателя. Алгоритм описан здесь а>. Суть алгоритма Виттера заключается в том, что после каждого розыгрыша вы вычисляете случайный пропуск, используя определенное распределение, которое гарантирует однородную выборку.
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;
}
См. Мой ответ на странице https://stackoverflow.com/a/46807110/8794687
Это один из простейших алгоритмов со средней временной сложностью O (s log s), s, обозначающий размер образца. Там также есть ссылки на алгоритмы хэш-таблиц, сложность которых, как утверждается, составляет O (s).
Кто-то написал "создание случайных чисел в excel". Я использую этот идеал. Создайте структуру из 2 частей, 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 );
}

O(n)по времени или памяти), то многие из приведенных ниже ответов неверны, включая принятый ответ. - person jww   schedule 29.08.2014O(1), если в ответеNэлементов. Так что требования неясны. - person ivan_pozdeev   schedule 05.09.2016