Уникальные (неповторяющиеся) случайные числа в O (1)?

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


person dicroce    schedule 12.10.2008    source источник
comment
Разве это не тот же вопрос, что и stackoverflow.com/questions/158716/   -  person jk.    schedule 13.10.2008
comment
0 находится между 0 и 1000?   -  person Pete Kirkham    schedule 19.01.2009
comment
Если вы запрещаете что-либо в течение постоянного времени (например, O(n) по времени или памяти), то многие из приведенных ниже ответов неверны, включая принятый ответ.   -  person jww    schedule 29.08.2014
comment
Как бы вы перетасовали колоду карт?   -  person Colonel Panic    schedule 24.12.2014
comment
У вас не может быть O(1), если в ответе N элементов. Так что требования неясны.   -  person ivan_pozdeev    schedule 05.09.2016
comment
ПРЕДУПРЕЖДЕНИЕ! Многие из приведенных ниже ответов не создают действительно случайных последовательностей, медленнее, чем O (n), или имеют другие дефекты! Перед тем как прочитать codinghorror.com/blog/archives/001015.html вы используете любой из них или пытаетесь придумать свой собственный!   -  person ivan_pozdeev    schedule 08.09.2016
comment
Пометка как неполноценный дубликат согласно meta.stackoverflow.com/questions/334325/   -  person ivan_pozdeev    schedule 29.10.2016


Ответы (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, и процесс может продолжаться.

person Robert Gamble    schedule 12.10.2008
comment
Если массив не начинается с перемешивания (и не с помощью наивного алгоритма; см. Сообщение Джеффа о перемешивании), разве это не вносит тонкую предвзятость? - person Mitch Wheat; 03.01.2009
comment
Без предвзятости. Это просто расширенная версия того же алгоритма. - person recursive; 03.01.2009
comment
Сообщение Джеффа о перемешивании предполагает, что это не вернет хорошие случайные числа .. codinghorror.com/blog/ archives / 001015.html - person pro; 03.01.2009
comment
@ Питер Раунс: Думаю, что нет; мне это кажется алгоритмом Фишера Йейтса, также цитируемого в сообщении Джеффа (как хорошего парня). - person Brent.Longborough; 03.01.2009
comment
Perl6 / Parrot будет иметь ленивые списки, которые будут отслеживать только измененные элементы. Это означает, что у них не будет начального удара, который был бы у других систем. - person Brad Gilbert; 04.01.2009
comment
@Brad: Я не совсем понимаю, о чем вы говорите. О каком начальном ударе вы говорите и как Perl6 / Parrot точно этого избежит? - person Robert Gamble; 04.01.2009
comment
Описанный вами алгоритм требует времени O (n log n) для создания списка или O (log n) амортизированного времени для каждого элемента. - person Charles; 25.09.2010
comment
@ Чарльз: Как вы это придумали? - person Robert Gamble; 25.09.2010
comment
@Robert: Потому что вы генерируете (lg n) -битное случайное число для каждого числа в списке, и это занимает время Omega (log n). - person Charles; 25.09.2010
comment
@ Чарльз: я думаю, что ваша логика ошибочна, может потребоваться время O (n) для создания исходного списка чисел, если у вас еще нет этой настройки, но требуется время O (1) для создания каждого числа из списка, который это то, что просили. - person Robert Gamble; 25.09.2010
comment
@Robert: Этот метод не создает список за O (n), он занимает O (n log n). Я не могу представить, что запрос означал, что нам разрешено бесконечное время предварительной обработки, потому что в этом случае работает любой метод - просто делайте все, что вам нужно, затем сгенерируйте каждый элемент и сохраните его в таблице. Мне ясно, что время должно включать в себя авансовую стоимость, которая составляет log n на элемент. (Фактически, алгоритм может быть изменен, чтобы генерировать каждый элемент за O (log n) время, а не за O (log n) амортизированное время.) - person Charles; 25.09.2010
comment
@Charles: для инициализации начального списка требуется O (n) времени (для инициализации 2 целых чисел одинакового размера требуется в два раза больше времени, чем для инициализации одного такого целого числа). Время, необходимое для генерации случайных чисел, зависит от реализации. Когда у вас есть случайное число, требуется постоянное время для выполнения представленного мною алгоритма. OP попросил что-то, что не прибегает к чему-то вроде поиска предыдущих значений O (N), чтобы сделать это, что указывает на то, что часть O (1) должна быть выбором. Поскольку это принятый ответ, я думаю, что он соответствует требованиям OP. - person Robert Gamble; 25.09.2010
comment
@Robert: Для этого метода требуются случайные биты Theta (n log n): на самом деле именно lg n! биты. Вы утверждаете, что можете генерировать биты Theta (n log n) за время O (n)? - person Charles; 26.09.2010
comment
@ Чарльз, претензия - это то, что я вложил в свой ответ. Прочтите вопрос и мой ответ, если вы считаете, что у вас есть лучший ответ, вы можете опубликовать его. - person Robert Gamble; 26.09.2010
comment
@robert: Я просто хотел указать, что он не производит, как в названии вопроса, уникальных случайных чисел в O (1). - person Charles; 26.09.2010
comment
@Charles: Достаточно честно, хотя никто никогда не говорил, что это так, и вопрос довольно хорошо раскрывает желаемое, что должно предотвратить путаницу, хотя я думаю, что заголовок можно обновить, чтобы лучше отразить вопрос, который на самом деле был задан и на который был дан ответ. - person Robert Gamble; 26.09.2010
comment
@ Роберт: Ага. (Честно говоря, я не утверждал, что кто-то утверждал, что это O (1) - я просто указал на сложность, поскольку до этого момента об этом не упоминалось.) - person Charles; 26.09.2010
comment
@ Charles / Robert - предполагая, что вы имеете дело с 32-битными машинными целыми числами фиксированного размера (что является разумным предположением, учитывая обсуждаемый вопрос и масштабы целых чисел), тогда вам нужно не более 32 случайных битов на выборку. Его создание является операцией O (1), поэтому для практических (в отличие от теоретических) целей его следует рассматривать как алгоритм O (1). - person mikera; 27.09.2010
comment
@mikera: Согласен, хотя технически, если вы используете целые числа фиксированного размера, весь список может быть сгенерирован за O (1) (с большой константой, а именно 2 ^ 32). Кроме того, для практических целей важно определение случайности - если вы действительно хотите использовать пул энтропии вашей системы, пределом является вычисление случайных битов, а не сами вычисления, и в этом случае снова имеет значение n log n. Но в вероятном случае, когда вы будете использовать (эквивалент) / dev / urandom, а не / dev / random, вы вернетесь к «практически» O (n). - person Charles; 27.09.2010
comment
Я немного сбит с толку, разве тот факт, что вам нужно выполнять N итераций (11 в этом примере), чтобы каждый раз получать желаемый результат, не означает, что это O(n)? Поскольку вам нужно сделать N итераций, чтобы получить N! комбинаций из одного и того же начального состояния, иначе ваш результат будет только одним из N состояний. - person Seph; 04.12.2011
comment
Понятно, что это алгоритм Гэмбла-Дюрстенфельда-Фишера-Йейтса :) - person gbarry; 26.02.2014
comment
Совершенно необязательно выполнять все 11 итераций перед тем, как начать заново. Таким образом, метод строго уступает методу Фишера-Йейтса без выигрыша. Именно то, о чем Джефф предупреждал в своем блоге , как указано pro . - person ivan_pozdeev; 08.09.2016

Ты можешь сделать это:

  1. Создайте список 0..1000.
  2. Перемешайте список. (См. перемешивание Фишера-Ятса, чтобы узнать, как это сделать.)
  3. Вернуть номера по порядку из перетасованного списка.

Таким образом, для этого не требуется каждый раз поиск старых значений, но для начального перемешивания по-прежнему требуется O (N). Но, как указал Нильс в комментариях, это амортизируется O (1).

person Chris Jester-Young    schedule 12.10.2008
comment
Я не согласен с тем, что это амортизировано. Общий алгоритм равен O (N) из-за перетасовки. Я думаю, вы могли бы сказать, что это O (.001N), потому что каждое значение потребляет только 1/1000 от перетасовки O (N), но это все еще O (N) (хотя и с крошечным коэффициентом). - person Kirk Strauser; 14.10.2008
comment
@ Just Some Guy N = 1000, значит, вы говорите, что это O (N / N), которое равно O (1) - person Guvante; 22.10.2008
comment
Если каждая вставка в перетасованный массив является операцией, то после вставки 1 значения вы можете получить 1 случайное значение. 2 для 2 значений и так далее, n для n значений. Для создания списка требуется n операций, поэтому весь алгоритм составляет O (n). Если вам нужно 1000000 случайных значений, потребуется 1000000 операций. - person Kibbee; 03.01.2009
comment
Подумайте об этом так: если бы время было постоянным, для 10 случайных чисел потребовалось бы такое же количество времени, как и для 10 миллиардов. Но из-за перетасовки, принимающей O (n), мы знаем, что это не так. - person Kibbee; 03.01.2009
comment
Любой метод, требующий инициализации массива размером N со значениями, будет иметь стоимость инициализации O (N); перемещение тасования к инициализации или выполнение одной итерации тасования для каждого запроса не имеет никакого значения. - person Pete Kirkham; 19.01.2009
comment
На самом деле это занимает амортизированное время O (log n), так как вам нужно сгенерировать n lg n случайных битов. - person Charles; 25.09.2010
comment
В течение многих лет я испытывал искушение внести поправку в правку @ AdamRosenfield, указав амортизированную, а не амортизированную, но я не могу заставить себя сделать такое небольшое правка без каких-либо других изменений, тем более что указанной правке 5 лет. Тем не менее, я должен по крайней мере заявить об этом для протокола. - person Chris Jester-Young; 19.12.2013
comment
И теперь у меня есть все основания для этого! meta.stackoverflow.com/q/252503/13 - person Chris Jester-Young; 08.05.2014

Используйте регистр сдвига максимальной линейной обратной связи.

Это реализуемо в нескольких строках C и во время выполнения делает немного больше, чем пара тестов / веток, небольшое добавление и битовый сдвиг. Это не случайно, но большинство людей вводит в заблуждение.

person plinth    schedule 14.10.2008
comment
Это не случайно, но большинство людей вводит в заблуждение. Это относится ко всем генераторам псевдослучайных чисел и всем возможным ответам на этот вопрос. Но большинство людей не думает об этом. Так что пропуск этого примечания может привести к большему количеству голосов ... - person f3lix; 18.03.2009
comment
Зачем подделывать это, если вы можете сделать это правильно? - person bobobobo; 18.04.2013
comment
@bobobobo: O (1) память - вот почему. - person Ash; 20.04.2013
comment
Чтобы сэкономить 4 КБ ОЗУ? Для этой конкретной проблемы я не вижу причин подделывать ее. Возможно, другие проблемы. - person bobobobo; 20.04.2013
comment
Nit: это память O (log N). - person Paul Hankin; 27.04.2013
comment
Как с помощью этого метода генерировать числа, скажем, от 0 до 800000? Некоторые могут использовать LFSR с периодом 1048575 (2 ^ 20-1) и получить следующий, если число выходит за пределы допустимого диапазона, но это не будет эффективно. - person tigrou; 04.07.2016
comment
Как LFSR, это не создает равномерно распределенные последовательности : вся последовательность, которая будет сгенерирована, определяется первым элементом. - person ivan_pozdeev; 08.09.2016

Вы можете использовать линейный конгруэнтный генератор. Где m (модуль) будет ближайшим простым числом больше 1000. Когда вы получите число за пределами диапазона, просто получите следующее. Последовательность будет повторяться только после того, как все элементы будут выполнены, и вам не нужно использовать таблицу. Однако помните о недостатках этого генератора (включая отсутствие случайности).

person Paul de Vrieze    schedule 12.10.2008
comment
1009 - первое простое число после 1000. - person Teepeemm; 08.05.2014
comment
LCG имеет высокую корреляцию между последовательными числами, поэтому комбинации не будут совершенно случайными в целом (например, числа, находящиеся дальше чем k в последовательности, никогда не могут встречаться вместе). - person ivan_pozdeev; 08.09.2016
comment
m должно быть количеством элементов 1001 (1000 + 1 для нуля), и вы можете использовать Next = (1002 * Current + 757) mod 1001; - person Max Abramovich; 17.12.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
...   ...

Чтобы получить разные неповторяющиеся псевдослучайные последовательности, измените ключ шифрования. Каждый ключ шифрования создает различную неповторяющуюся псевдослучайную последовательность.

person Craig McQueen    schedule 19.04.2013
comment
По сути, это простое сопоставление, поэтому оно не отличается от LCG и LFSR, со всеми соответствующими перегибами (например, значения, разнесенные более чем на k в последовательности, никогда не могут встречаться вместе). - person ivan_pozdeev; 08.09.2016
comment
@ivan_pozdeev: Мне сложно понять смысл вашего комментария. Можете ли вы объяснить, что не так с этим отображением, каковы все существенные изломы и что такое k? - person Craig McQueen; 08.09.2016
comment
Все, что здесь эффективно при шифровании, - это замена последовательности 1,2,...,N последовательностью тех же чисел в каком-то другом, но все еще постоянном порядке. Затем числа извлекаются из этой последовательности один за другим. k - это количество выбранных значений (OP не указывал для него букву, поэтому мне пришлось ввести ее). - person ivan_pozdeev; 08.09.2016
comment
Поскольку последовательность постоянна и каждое число в ней уникально, возвращаемая комбинация полностью определяется первым числом. Таким образом, он не является полностью случайным - можно сгенерировать лишь небольшое подмножество возможных комбинаций. - person ivan_pozdeev; 08.09.2016
comment
LFSR и LCG имеют одно и то же свойство, поэтому применимы и другие связанные с ним дефекты. - person ivan_pozdeev; 08.09.2016
comment
Понятно. Однако в случае шифрования с сохранением формата вы можете получить различную случайную последовательность для каждого выбранного ключа шифрования. С 128-битным или 256-битным ключом, это дает вам множество возможных последовательностей. Для любого выбранного ключа сгенерированная последовательность гарантированно будет полным набором эффективно перемешанных чисел. - person Craig McQueen; 09.09.2016
comment
Что касается значений, превышающих k отдельно, вы просто выбираете подходящую систему счисления r и ширину w в соответствии с вашими потребностями, чтобы получить k = rʷ, а затем генерируете столько n значений, сколько хотите, где n ≤ k. Например, в своем ответе я показал, как получить 16 неповторяющихся чисел в диапазоне 0..999. - person Craig McQueen; 09.09.2016
comment
Конечно, недостатки ГПСЧ постоянной последовательности (сильная корреляция между результатами) проявляются только тогда, когда вы используете одну и ту же настройку более одного раза. Если вы используете его один раз и больше никогда, это нормально. Но создание новой настройки каждый раз (случайным образом, с лучшим ГСЧ!) В значительной степени сводит на нет цель создания собственного ГПСЧ. - person ivan_pozdeev; 10.09.2016
comment
Это потому, что ключ примерно равен тому же количеству хороших случайных чисел (должен иметь N! возможных значений, чтобы охватить все возможные последовательности) в дополнение ко всей дополнительной работе. - person ivan_pozdeev; 10.09.2016
comment
@ivan_pozdeev Дело не в том, что FPE должен реализовывать конкретное статическое сопоставление или что возвращаемая комбинация полностью определяется первым числом. Поскольку параметр конфигурации намного больше, чем размер первого числа (которое имеет только тысячу состояний), должно быть несколько последовательностей, которые начинаются с одного и того же начального значения, а затем переходят к другим последующим значениям. Любой реалистичный генератор не сможет охватить все возможное пространство перестановок; не стоит поднимать этот режим отказа, если ОП не просил об этом. - person sh1; 11.09.2016
comment
+1. При правильной реализации с использованием безопасного блочного шифра с равномерно выбранным случайным ключом последовательности, сгенерированные с помощью этого метода, будут вычислительно неотличимы от истинного случайного тасования. Другими словами, нет способа отличить вывод этого метода от истинного случайного тасования значительно быстрее, чем путем тестирования всех возможных ключей блочного шифрования и проверки, генерирует ли какой-либо из них тот же вывод. Для шифра с 128-битным пространством ключей это, вероятно, превышает вычислительную мощность, доступную в настоящее время человечеству; с 256-битными ключами, вероятно, так и останется навсегда. - person Ilmari Karonen; 12.09.2016
comment
@IlmariKaronen Тем не менее, ответ неполный без указания того, что нужен новый случайный ключ для каждой перестановки, некоторые точки его требуемой длины и генерирует не все возможные перестановки, а неразличимую речь. - person ivan_pozdeev; 12.09.2016
comment
Может быть, это не совсем то, что хочет спрашивающий, но это то, что я ищу;) - person tactoth; 11.08.2017

Для небольших чисел, таких как 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, обратим. Это навеяно шифром Фейстеля.

person sellibitze    schedule 22.06.2010
comment
То же, что и stackoverflow.com/a/16097246/648265, точно так же не работает случайность для последовательностей. - person ivan_pozdeev; 11.09.2016
comment
@ivan_pozdeev: Теоретически, учитывая бесконечную вычислительную мощность, да. Однако, если предположить, что hash(), используемый в приведенном выше коде, является безопасной псевдослучайной функцией, эта конструкция доказуемо (Luby & Rackoff, 1988) даст псевдослучайная перестановка, которую нельзя отличить от истинной случайной перестановки, требующей значительно меньших усилий, чем исчерпывающий поиск всего ключевого пространства, длина которого экспоненциальна. Даже для ключей разумного размера (скажем, 128 бит) это превышает общую вычислительную мощность, доступную на Земле. - person Ilmari Karonen; 12.09.2016
comment
(Кстати, чтобы сделать этот аргумент более строгим, я бы предпочел заменить специальную конструкцию 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)
  • Несколько моих игр для симуляторов, "перетасовок", рейтинговых
  • более

Это открыто, бесплатно. Попробуйте ...

person Tod Samay    schedule 19.12.2013
comment
Может ли этот метод работать с десятичным значением, например скремблировать трехзначный десятичный счетчик, чтобы всегда иметь трехзначный десятичный результат? - person Craig McQueen; 14.08.2016
comment
В качестве примера алгоритма Xorshift это алгоритм LFSR со всеми связанными перегибами (например, значениями больше, чем k по отдельности в последовательности никогда не могут встречаться вместе). - person ivan_pozdeev; 08.09.2016

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

введите описание изображения здесь

и есть только 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
person Max Abramovich    schedule 17.12.2016

Вам даже не нужен массив, чтобы решить эту проблему.

Вам нужна битовая маска и счетчик.

Установите счетчик на ноль и увеличивайте его при последующих вызовах. Выполните XOR счетчика с битовой маской (выбираемой случайным образом при запуске или фиксированной) для генерации псевдослучайного числа. Если у вас не может быть чисел, превышающих 1000, не используйте битовую маску шире 9 бит. (Другими словами, битовая маска - это целое число не выше 511.)

Убедитесь, что когда счетчик достигнет 1000, вы сбросите его на ноль. В это время вы можете выбрать другую случайную битовую маску, если хотите получить тот же набор чисел в другом порядке.

person Max    schedule 03.01.2009
comment
Это обманет меньше людей, чем LFSR. - person starblue; 22.10.2009
comment
битовая маска в пределах 512 ... 1023 тоже в порядке. Чтобы узнать больше о фальшивой случайности, см. Мой ответ. :-) - person sellibitze; 22.06.2010
comment
По сути эквивалентен stackoverflow.com/a/16097246/648265, также не соответствует случайности для последовательностей. - person ivan_pozdeev; 08.09.2016

Вот набранный мной код, использующий логику первого решения. Я знаю, что это «агностик языка», но просто хотел представить это в качестве примера на 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]);
}
person firedrawndagger    schedule 25.08.2010

Результаты этого метода подходят, когда лимит высок, и вы хотите сгенерировать только несколько случайных чисел.

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

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

person salva    schedule 19.01.2012
comment
Поскольку это генерирует комбинации, а не перестановки, это более подходит для stackoverflow.com/questions/2394246/ - person ivan_pozdeev; 11.09.2016
comment
Тестирование показывает, что это имеет тенденцию к меньшим числам: измеренные вероятности для 2M выборок с (top,n)=(100,10) равны: (0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635). Я тестировал на Python, поэтому небольшие различия в математике могут здесь сыграть роль (я убедился, что все операции для вычисления r выполняются с плавающей запятой). - person ivan_pozdeev; 11.09.2016
comment
Да, для правильной работы этого метода верхний предел должен быть намного больше, чем количество извлекаемых значений. - person salva; 12.09.2016
comment
Это не будет работать правильно, даже если верхний предел [] намного больше, чем количество значений. Вероятности по-прежнему будут неравномерными, только с меньшей разницей. - person ivan_pozdeev; 16.12.2017

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

Из здесь мы получаем дизайн для 10-битного ГПСЧ ..

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

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

person pro    schedule 03.01.2009
comment
@Phob Нет, этого не произойдет, потому что 10-битный ГПСЧ, основанный на регистре сдвига с линейной обратной связью, обычно создается из конструкции, которая принимает все значения (кроме одного) один раз, прежде чем вернуться к первому значению. Другими словами, он выберет 1001 только один раз за цикл. - person Nuoji; 23.03.2013
comment
@Phob весь смысл этого вопроса состоит в том, чтобы выбрать каждое число ровно один раз. А потом вы жалуетесь, что 1001 не встречается дважды подряд? LFSR с оптимальным спредом будет проходить все числа в своем пространстве псевдослучайным образом, а затем перезапустить цикл. Другими словами, он не используется как обычная случайная функция. При случайном использовании мы обычно используем только подмножество битов. Прочтите немного об этом, и скоро все станет понятно. - person Nuoji; 19.04.2013
comment
Единственная проблема заключается в том, что данный LFSR имеет только одну последовательность, что дает сильную корреляцию между выбранными числами - в частности, не генерирует все возможные комбинации. - person ivan_pozdeev; 08.09.2016

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), если требуется.
Примечание. Случайное число должно быть статическим с применением потоковой безопасности.

person Erez Robinson    schedule 23.05.2012
comment
O (n ^ 2), поскольку количество повторных попыток в среднем пропорционально количеству выбранных элементов. - person ivan_pozdeev; 08.09.2016
comment
Подумайте об этом, если вы выберете min = 0 max = 10000000 и N = 5, будет повторяться ~ = 0 независимо от того, сколько было выбрано. Но да, вы понимаете, что если max-min мало, o (N) распадается. - person Erez Robinson; 09.09.2016
comment
Если N ‹< (макс-мин), то все равно пропорционально, просто коэффициент очень маленький. А для асимптотической оценки коэффициенты значения не имеют. - person ivan_pozdeev; 10.09.2016
comment
Это не O (n). Каждый раз, когда набор содержит это значение и дополнительный цикл. - person paparazzo; 01.03.2017

Вот пример кода 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.
person Myron Denson    schedule 22.02.2015
comment
Я понятия не имею, действительно ли это может удовлетворить потребности OP, но поддерживает вклад COBOL! - person Mac; 27.05.2018

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

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

  2. Перемешивание списка A с использованием Fisher-Yates занимает n времени.

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

  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
person Khaled.K    schedule 27.04.2016
comment
Необязательно вести 2 списка - или исчерпать список перед тем, как смотреть снова. Фишер-Йейтс дает равномерно случайные результаты из любого начального состояния. См. Объяснение в stackoverflow.com/a/158742/648265. - person ivan_pozdeev; 08.09.2016
comment
@ivan_pozdeev Да, это тот же результат, но моя идея состоит в том, чтобы сделать его амортизированным O (1), сделав перетасовку частью действия рисования. - person Khaled.K; 08.09.2016
comment
Вы не поняли. Вам вообще не нужно сбрасывать список перед повторной перетасовкой. Перемешивание [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, если это максимум,

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

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

person Hans Olsson    schedule 03.07.2017

Еще одна возможность:

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

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

person Toon Krijthe    schedule 12.10.2008
comment
Это O (k ^ 2), с рядом дополнительных шагов, пропорциональных в среднем количеству выбранных значений. - person ivan_pozdeev; 08.09.2016

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

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;
}
person sh1    schedule 02.06.2015
comment
Это LCG, например stackoverflow.com/a/196164/648265, неслучайный для последовательностей, а также других связанных перегибы точно такие же. - person ivan_pozdeev; 11.09.2016
comment
Мой @ivan_pozdeev лучше, чем LCG, потому что он гарантирует, что он не вернет дубликат при 1001-м вызове. - person sh1; 11.09.2016

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

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

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

person Emanuel Landeholm    schedule 22.11.2016
comment
Ребят пожалуйста! Метод Фишера-Йейтса не работает. Вы выбираете первый с вероятностью 1 / N, а второй с вероятностью 1 / (N-1)! = 1 / N. Это необъективный метод выборки! Вам действительно нужен алгоритм Витттера для устранения систематической ошибки. - person Emanuel Landeholm; 07.03.2019

Фишер Йейтс

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;
}
person paparazzo    schedule 01.03.2017
comment
На это уже есть ответ, но он довольно длинный и не распознает, что вы можете остановиться на 1 (а не на 0) - person paparazzo; 01.03.2017

См. Мой ответ на странице https://stackoverflow.com/a/46807110/8794687

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

person Pavel Ruzankin    schedule 30.10.2017

Кто-то написал "создание случайных чисел в 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 );
}
person Grog Klingon    schedule 01.11.2019