Унікальні (неповторювані) випадкові числа в O (1)?


179

Я хотів би генерувати унікальні випадкові числа від 0 до 1000, які ніколи не повторюються (тобто 6 не з’являються двічі), але це не вдається до чогось подібного пошуку O (N) попередніх значень, щоб це зробити. Чи можливо це?


4
Це не те саме питання, що і stackoverflow.com/questions/158716/…
jk.

2
Чи 0 між 0 і 1000?
Піт Кіркхем

4
Якщо ви забороняєте що-небудь протягом постійного часу (наприклад, O(n)у часі чи пам’яті), багато хто з наведених нижче відповідей є помилковими, включаючи прийняту відповідь.
jww

Як би ви пересунули пакет карт?
Полковник Паніка

9
УВАГА! Багато відповідей, поданих нижче, не створюють справді випадкових послідовностей , повільніше, ніж O (n) або інакше несправні! codinghorror.com/blog/archives/001015.html є важливою ознакою , перш ніж використовувати будь-яку з них або намагатися придумати своє!
ivan_pozdeev

Відповіді:


247

Ініціалізуйте масив з 1001 цілих чисел зі значеннями 0-1000 і встановіть змінну, max, на поточний максимальний індекс масиву (починаючи з 1000). Виберіть випадкове число, r, між 0 і max, поміняйте номер у позиції r на число в положенні max і поверніть це число в позицію max. Зменшення макс на 1 і продовжуйте. Коли max дорівнює 0, встановіть max назад на розмір масиву - 1 і почніть знову, не потребуючи повторної ініціалізації масиву.

Оновлення: Хоча я сам придумав цей метод, коли відповів на це питання, але після деяких досліджень я розумію, що це модифікована версія Фішера-Йейта, відома як Дурстенфельд-Фішер-Йейтс або Кнут-Фішер-Йейтс. Оскільки опису може бути трохи важко дотримуватися, я наводив приклад нижче (використовуючи 11 елементів замість 1001):

Масив починається з 11 елементів, ініціалізованих до масиву [n] = n, макс починається з 10:

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

При кожній ітерації вибирається випадкове число r між 0 і max, масив [r] і масив [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
pro

14
@Peter Rounce: Я думаю, що ні; це схоже на мене як на алгоритм Фішера Йейтса, який також цитується у публікації Джеффа (як хороший хлопець).
Brent.Longborough

3
@robert: Я просто хотів зазначити, що це не дає, як в назві питання, "унікальних випадкових чисел в O (1)".
Чарльз

3
@mikera: Погоджено, хоча технічно, якщо ви використовуєте цілі числа фіксованого розміру, весь список може бути сформований в O (1) (з великою постійною, а саме 2 ^ 32). Також для практичних цілей важливим є визначення "випадкового" - якщо ви дійсно хочете використовувати пул ентропії вашої системи, ліміт - це обчислення випадкових бітів, а не самих обчислень, і в цьому випадку n log n має значення знову. Але у ймовірному випадку, що ви будете використовувати (еквівалент) / dev / urandom, а не / dev / random, ви повернетесь до "практично" O (n).
Чарльз

4
Я трохи розгублений, чи не те, що вам доведеться виконувати Nітерації (11 у цьому прикладі), щоб отримувати бажаний результат кожного разу, означає це O(n)? Як вам потрібно зробити Nітерації, щоб отримати N!комбінації з одного і того ж початкового стану, інакше ваш вихід буде лише одним із N станів.
Seph

71

Ви можете зробити це:

  1. Створіть список, 0..1000.
  2. Перемішайте список. (Див. Переміщення Фішера-Йейта для того, щоб це зробити добре.)
  3. Повертайте номери в порядку зі змішаного списку.

Отже, це не вимагає пошуку старих значень щоразу, але для початкового переміщення все одно потрібно O (N). Але як зазначав Нілс у коментарях, це амортизується O (1).


5
@ Просто деякий хлопець N = 1000, тож ви говорите, що саме O (N / N) є O (1)
Гуванте,

1
Якщо кожна вставка в перетасований масив є операцією, то після вставки 1 значення можна отримати 1 випадкове значення. 2 для 2 значень тощо, n для n значень. Для генерування списку потрібно n операцій, тому весь алгоритм - O (n). Якщо вам потрібно
1000

3
Подумайте про це так, якби це був постійний час, це знадобило б стільки ж часу для 10 випадкових чисел, як і 10 мільярдів. Але через переміщення, що приймає O (n), ми знаємо, що це неправда.
Kibbee

1
Це фактично займає амортизований час O (log n), оскільки вам потрібно генерувати n lg n випадкових біт.
Чарльз

2
І тепер у мене є все виправдання це робити! meta.stackoverflow.com/q/252503/13
Chris Jester-Young

60

Використовуйте максимальний реєстр лінійних зворотних зв'язків .

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


12
"Це не випадково, але це обдурює більшість людей". Це стосується всіх генераторів псевдовипадкових чисел та всіх можливих відповідей на це питання. Але більшість людей не думають про це. Тож пропускання цієї ноти, можливо, призведе до більшої кількості оновлень ...
f3lix

3
@bobobobo: О (1) пам'ять є чому.
Ясен

3
Ніт: це O (log N) пам'яті.
Пол Ханкін

2
Використовуючи цей метод, як ви генеруєте числа, скажімо, від 0 до 800000? Деякі можуть скористатися LFSR, період якого 1048575 (2 ^ 20 - 1), і отримати наступний, якщо число буде поза діапазоном, але це не буде ефективно.
tigrou

1
Як LFSR, це не створює рівномірно розподілених послідовностей: вся послідовність, яка буде створена, визначається першим елементом.
ivan_pozdeev

21

Ви можете використовувати лінійний конгрурентний генератор . Де m(модуль) буде найближчим простим числом більше 1000. Коли ви вийдете число з діапазону, просто отримайте наступне. Послідовність повторюється лише після того, як всі елементи відбудуться, і вам не доведеться використовувати таблицю. Будьте в курсі недоліків цього генератора (включаючи відсутність випадковості).


1
1009 - це перший прем'єр після 1000.
Teepeemm

LCG має високу кореляцію між послідовними числами, тому комбінації не будуть зовсім випадковими (наприклад, числа, що більше, ніж kоднакові в послідовності, ніколи не можуть відбуватися разом).
ivan_pozdeev

m має бути кількість елементів 1001 (1000 + 1 за нуль), і ви можете використовувати Next = (1002 * Поточний + 757) mod 1001;
Макс Абрамович

21

Ви можете використовувати шифрування для збереження формату для шифрування лічильника. Ваш лічильник просто переходить від 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
...   ...

Щоб отримати різні не повторювані псевдовипадкові послідовності, змініть ключ шифрування. Кожен ключ шифрування створює іншу неповторювану псевдовипадкову послідовність.


Це по суті просте відображення, таким чином, нічим не відрізняється від LCG та LFSR, з усіма відповідними перемичками (наприклад, значення, більші за kодин раз у послідовності, ніколи не можуть зустрічатися разом).
ivan_pozdeev

@ivan_pozdeev: У мене складно зрозуміти значення вашого коментаря. Чи можете ви пояснити, що з цим картографуванням не так, що таке "всі відповідні перегини" та що таке k?
Крейг МакКуїн

Тут ефективно "шифрування" - це замінити послідовність 1,2,...,Nпослідовністю однакових чисел у якомусь іншому, але все ж постійному порядку. Потім числа витягуються з цієї послідовності по черзі. k- це кількість вибраних значень (в ОП не було вказано літери для цього, тому мені довелося ввести одне).
ivan_pozdeev

3
@ivan_pozdeev Це не так, що FPE повинен реалізувати певне статичне відображення, або що "повернення, що повертається, повністю визначається першим числом". Оскільки параметр конфігурації значно більший, ніж розмір першого числа (який має лише тисячу станів), повинно бути кілька послідовностей, які починаються з одного і того ж початкового значення, а потім переходять до різних наступних значень. Будь-який реалістичний генератор не зможе покрити весь можливий простір перестановок; не варто піднімати цей режим відмов, коли ОП цього не просили.
sh1

4
+1. Якщо правильно реалізовуватись, використовуючи захищений блок-шифр з ключем, вибраним рівномірно, послідовності, згенеровані за допомогою цього методу, обчислювально не відрізнятимуться від справжнього випадкового переміщення. Тобто, немає способу відрізнити вихід цього методу від справжнього випадкового переміщення значно швидше, ніж тестування всіх можливих клавіш блокових шифрів і перевірка того, чи хтось із них генерує той самий вихід. Для шифру з 128-бітовим простором клавіш, це, мабуть, перевищує обчислювальну потужність, доступну зараз людству; з 256-бітними ключами, це, ймовірно, назавжди залишиться таким.
Ільмарі Каронен

7

Для малих чисел, таких як 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, біт) -1, припускаючи фіксовану клавішу. Це випливає з побудови, тому що кожен крок, який змінює змінну, indexє оборотним. На це надихає шифр Feistel .


Так само, як stackoverflow.com/a/16097246/648265 , не вдається випадковість для послідовностей так само.
ivan_pozdeev

1
@ivan_pozdeev: Теоретично, якщо припускати нескінченну обчислювальну потужність, так. Однак, якщо припустити, що hash(), як використано у наведеному вище коді, є захищеною псевдовипадковою функцією, ця конструкція буде по-видимому (Luby & Rackoff, 1988) призведе до псевдовипадкової перестановки , яку не можна відрізнити від справжньої випадкової перестановки, використовуючи значно менші зусилля, ніж вичерпні пошук всього простору ключів, який є експоненціальним по довжині ключа. Навіть для клавіш із розумним розміром (скажімо, 128 біт) це перевищує загальну обчислювальну потужність, наявну на Землі.
Ільмарі Каронен

(BTW, щоб зробити цей аргумент трохи більш суворим, я вважаю за краще замінити ad hoc hash( key + "/" + int2str(temp) )конструкцію вище HMAC , безпека якої, в свою чергу, може бути значно знижена до рівня основної функції стиснення хешу. Також використання HMAC може зробити менша ймовірність, що хтось помилково спробує використати цю конструкцію з небезпечною
некриптовою

6

Ви можете використовувати описаний тут мій алгоритм Xincrol:

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

Це чистий алгоритмічний метод генерації випадкових, але унікальних чисел без масивів, списків, перестановок або великого навантаження на процесор.

Остання версія дозволяє також встановити діапазон чисел, наприклад, якщо я хочу унікальні випадкові числа в діапазоні 0-1073741821.

Я практично його використовував

  • MP3-програвач, який відтворює кожну пісню випадковим чином, але лише один раз на альбом / каталог
  • Ефект розчинення піксельних мудрець для пікселів (швидкий і плавний)
  • Створення таємного "шумового" туману над зображенням для підписів та маркерів (стеганографія)
  • Ідентифікатори об'єктів даних для серіалізації величезної кількості об'єктів Java через Бази даних
  • Захист бітів пам'яті потрійної більшості
  • Шифрування адреси + значення (кожен байт не просто шифрується, але також переміщується до нового зашифрованого місця в буфері). Це справді змусило мене з криптоаналізу злитися з розуму :-)
  • Звичайний текст до простого шифрування криптовалютного тексту для SMS, електронної пошти тощо
  • Мій Техаський Холдем` Калькулятор покеру (THC)
  • Кілька моїх ігор для симуляцій, «перетасування», рейтингу
  • більше

Це відкрито, безкоштовно. Спробувати...


Чи може цей метод працювати для десяткового значення, наприклад, розшифровка трицифрового десяткового лічильника, щоб завжди мати 3-значний десятковий результат?
Крейг МакКуін

Як приклад алгоритму Xorshift , це LFSR з усіма спорідненими перемичками (наприклад, значення, більші за kодин раз у послідовності, ніколи не можуть зустрічатися разом).
ivan_pozdeev

5

Для вирішення цього вам навіть не потрібен масив.

Вам потрібна бітмаска та лічильник.

Ініціалізуйте лічильник до нуля та збільшуйте його на послідовних дзвінках. XOR лічильник з бітовою маскою (випадковим чином обраний при запуску або фіксований), щоб генерувати psuedorandom число. Якщо у вас немає чисел, що перевищують 1000, не використовуйте бітову маску ширше 9 біт. (Іншими словами, бітова маска є цілим числом не вище 511.)

Переконайтесь, що коли лічильник проходить 1000, ви скинете його до нуля. На даний момент ви можете вибрати іншу випадкову біт-маску - якщо вам подобається - створити однаковий набір чисел у іншому порядку.


2
Це обдурить менше людей, ніж LFSR.
starblue

"бітмаска" в межах 512 ... 1023 теж добре. Для трохи більше фальшивих випадковостей дивіться мою відповідь. :-)
sellibitze

По суті, еквівалентний stackoverflow.com/a/16097246/648265 , також не дає випадковості для послідовностей.
ivan_pozdeev

4

Я думаю, що лінійний конгрурентний генератор був би найпростішим рішенням.

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

і є тільки три обмеження на а , з і м значень

  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

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
Тестування показує це має ухил в бік більш низьких чисел: виміряні ймовірності для зразків з 2М (top,n)=(100,10)є: (0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635). Я перевірив Python, тому незначні відмінності в математиці можуть зіграти тут певну роль (я впевнився, що всі операції для обчислення rмають плаваючу крапку).
ivan_pozdeev

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

Він не буде працювати "правильно", навіть якщо "верхня межа [набагато більша, ніж кількість значень" . Ймовірності все ще будуть нерівномірними, лише з меншою відривкою.
ivan_pozdeev

2

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

З тут ми отримуємо дизайн для 10 бітного ПСЧА ..

  • 10 біт, поліном зворотного зв'язку x ^ 10 + x ^ 7 + 1 (період 1023)

  • використовувати швидкий код Galois LFSR


@Phob Ні, цього не відбудеться, оскільки 10-бітний PRNG, заснований на реєстрі зсуву лінійної зворотної зв'язку, зазвичай складається з конструкції, яка приймає всі значення (крім одного) один раз, перш ніж повернутися до першого значення. Іншими словами, він вибере 1001 рівно один раз протягом циклу.
Nuoji

1
@Робота всього питання цього питання полягає в тому, щоб вибрати кожне число рівно один раз. І тоді ви скаржитеся, що 1001 не відбуватиметься двічі поспіль? LFSR з оптимальним поширенням перемістить усі числа у своєму просторі псевдо випадковим чином, а потім перезапустить цикл. Іншими словами, він не використовується як звичайна випадкова функція. Коли ми використовуємо їх як випадкові випадки, ми зазвичай використовуємо лише підмножину бітів. Почитайте трохи про це, і незабаром це матиме сенс.
Нуоджі

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 незалежно від кількості вибраних. Але так, у вас є пункт про те, що якщо max-min невеликий, o (N) розпадається.
Ерез Робінсон

Якщо N << (max-min), то вона все ще пропорційна, просто коефіцієнт дуже малий. А коефіцієнти не мають значення для асимптотичної оцінки.
ivan_pozdeev

Це не O (n). Кожен раз, коли набір містить це значення та додатковий цикл.
папараццо

2

Скажімо, ви хочете переглядати перетасовані списки знову і знову, не маючи O(n)затримки кожен раз, коли ви починаєте перетасовувати його знову, і в такому випадку ми можемо це зробити:

  1. Створіть 2 списки А і В, від 0 до 1000, займає 2nмісце.

  2. Список перетасовок A, використовуючи Fisher-Yates, потребує nчасу.

  3. Намалювавши число, зробіть однотактне переміщення 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 списки - або вичерпувати список, перш ніж дивитися на нього. Фішер-Йейтс дає рівномірно випадкові результати з будь-якого початкового стану. Пояснення див. У розділі stackoverflow.com/a/158742/648265 .
ivan_pozdeev

@ivan_pozdeev Так, це той самий результат, але моя ідея тут полягає в тому, щоб зробити його амортизованим O (1), зробивши перетасування частиною дії малювання.
Халед.К

Ти не зрозумів. Вам не потрібно скидати список взагалі перед повторним переміщенням. Перемішування [1,3,4,5,2]дасть такий же результат, як і перетасування [1,2,3,4,5].
ivan_pozdeev

2

Питання Як ефективно генерувати список K не повторюваних цілих чисел від 0 до верхньої межі N , пов'язаний як дублікат - і якщо ви хочете щось, що є O (1) на генероване випадкове число (без O (n) вартість запуску)) є проста настройка прийнятої відповіді.

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

  1. Виберіть випадкове число, r, між 0 і макс.
  2. Переконайтесь, що обидва елементи карти r та max існують у не упорядкованій карті. Якщо їх немає, створіть їх зі значенням, рівним їх індексу.
  3. Змінюють елементи r і max
  4. Поверніть max та елемент max на 1 (якщо макс стає негативним, ви закінчили).
  5. Повернутися до кроку 1.

Єдина відмінність порівняно з використанням ініціалізованого масиву полягає в тому, що ініціалізація елементів відкладається / пропускається - але вона генерує однакові номери з того ж PRNG.


1

Ще одна можливість:

Можна використовувати масив прапорів. І візьміть наступний, коли його вже вибрали.

Але будьте обережні після 1000 дзвінків, функція ніколи не закінчиться, тому ви повинні зробити захист.


Це О (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!
Мак

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 інших виходів значення дозволяється повторювати, але це наївно дозволяє 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;
}

Це LCG, як stackoverflow.com/a/196164/648265 , не випадковий для послідовностей, а також інші пов'язані перегини саме такі.
ivan_pozdeev

@ivan_pozdeev мій кращий, ніж LCG, оскільки він гарантує, що він не поверне дублікат під час 1001-го виклику.
sh1

1

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

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

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


Хлопці, будь ласка! Метод Фішера-Йейта порушений. Ви вибираєте перший з ймовірністю 1 / N і другий з ймовірністю 1 / (N-1)! = 1 / N. Це упереджений метод вибірки! Вам дійсно потрібен алгоритм Віттера для вирішення зміщення.
Емануель Ландегольм

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), оскільки вам потрібен лише один своп для останніх двох
Це 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;
}

Відповідь на це вже є, але вона досить довго звивається і не визнає, що ви можете зупинитися на 1 (не 0)
папараццо

0

Перегляньте мою відповідь на веб- сайті https://stackoverflow.com/a/46807110/8794687

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


-1

Хтось розмістив "створення випадкових чисел у 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 );
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.