Найбільш ефективний алгоритм друку 1-100 за допомогою заданого генератора випадкових чисел


11

Нам дається генератор випадкових чисел, RandNum50який генерує випадкове ціле число рівномірно в межах 1–50. Ми можемо використовувати лише цей генератор випадкових чисел для генерування та друку всіх цілих чисел від 1 до 100 у випадковому порядку. Кожне число повинно входити рівно один раз, і ймовірність появи будь-якого числа в будь-якому місці повинна бути однаковою.

Який найефективніший алгоритм для цього?


1
Використовуйте масив / або бітовий вектор для запису вже бачених чисел та лічильник для запису кількості переглянутих унікальних чисел.
Дейв Кларк

@DaveClarke Як я можу генерувати число, яке перевищує 50? Якщо я використовую його більше 1 разу, то також, як я буду генерувати 1, використовуючи їх?
Raj Wadhwa

1
Завдання, звичайно, полягає в тому, щоб забезпечити, що всі місця з однаковою ймовірністю. Ви можете використовувати RandNum100 = (RandNum50() * 2) - (RandNum50 > 25) ? 0 : 1).
Дейв Кларк

2
@DaveClarke: Отже, ви пропонуєте повторення вибірки відхилень? Це закінчиться лише в очікуванні.
Рафаель

Я просто давав підказку.
Дейв Кларк

Відповіді:


3

Я подумав (тому це може бути неправильно :-) цього рішення яке використовує перемикання Фішера-Йейта . Щоб зберегти рівномірний розподіл з хорошим наближенням (див. Розділ EDIT нижче) при кожній ітерації, ви можете використовувати цей трюк для отримання значення між і :0 k - 1O(N2)krand0k1

 // return a random number in [0..k-1] with uniform distribution
 // using a uniform random generator in [1..50]
 funtion krand(k) {    
   sum = 0
   for i = 1 to k do sum = sum + RandNum50() - 1
   krand = sum mod k
 }

Алгоритм Фішера-Йейта стає:

arr : array[0..99]
for i = 0  to 99 do arr[i] = i+1; // store 1..100 in the array
for i = 99 downto 1 {
  r = krand(i+1)  // random value in [0..i]
  exchange the values of arr[i] and arr[r]
}
for i = 0 to 99 do print arr[i]

Редагувати:

Як вказував Ерік, krandнаведена вище функція не повертає справді рівномірного розподілу. Є й інші методи, які можна використовувати для кращого (довільно кращого) та швидшого наближення; але ( мені відомо) єдиний спосіб отримати по-справжньому рівномірне розподіл - використовувати вибірку відхилення : виберіть випадкові біти, і якщо отримане число менше поверне його, в іншому випадку генерується інше випадкове число; можлива реалізація:r km=log2(k)rk

function trulyrand(k) {
    if (k <= 1) return 0
    while (true) { // ... if you're really unlucky ...
      m = ceil(log_2 (k) ) // calculate m such that k < 2^m
      r = 0  // will hold the random value
      while (m >= 0) {  // ... will add m bits        
        if ( rand50() > 25 ) then b = 1 else b = 0   // random bit
        r = r * 2 + b  // shift and add the random bit
        m = m - 1
      }      
      if (r < k) then return r  // we have 0<=r<2^m ; accept it, if r < k
    }
}

1
На сторінці вікіпедії, на яку ви посилаєтесь, зазначено, що існує варіант . O(n)
Дейв Кларк

1
Я думаю, що "перетасування" є ключовим модником тут.
Рафаель

4
Трюк у krand (k) не дає по-справжньому рівномірного розподілу, хоча це гарне наближення: навіть для k = 3 це дає 33,333328% шанс вивести 0. Чи є виправдання для підсумовування всього до k тут ? Я думаю, що меншої межі достатньо, якщо ми просто хочемо наближення.
Ерік Вонг

1
@ErickWong: ти маєш рацію; Я думаю, що справжнього рівномірного розподілу можна досягти лише за допомогою методу відбору проб відхилення, який не гарантовано закінчується постійно. Є й інші схеми наближення (що дозволяє досягти будь-якого бажаного наближення), та, яку я запропонував, є першою, яка мені прийшла в голову.
Vor

2
@ ex0du5: Я це знаю, але як ви створюєте рівномірну випадкову перестановку чисел [1..100], використовуючи лише рівномірний випадковий генератор у [1..100]? Єдиний альтернативний метод, який я знаю, це: крок 1) вибрати випадкове значення в ; step2) якщо вже вибрано, відкиньте його та перейдіть до кроку1; step3) друк ; крок 4), якщо ми не надрукували всі 100 номерів, перейшовши на крок1. Але цей метод просто зміщує відмову на вже підібрані елементи. 1..100 r rr1..100rr
Vor

4

Оскільки інші люди дали приблизні рішення та рішення, пов’язані з невизначеним числом відхилень, як щодо доказу того, що не існує такого алгоритму, який гарантував би лише необхідну кількість RandNum50()викликів?

Як зазначали інші, друк чисел від 1-100 у випадковому порядку еквівалентний друку випадкової перестановки цих чисел; є 100! цих перестановок, і тому будь-яка конкретна перестановка повинна виводитися з вірогідністю .1100!

Але якби ми знали, що наш алгоритм використовує щонайбільше дзвінків для деякого , тоді ми могли б аргументувати наступне: по-перше, відкладіть ті контури обчислень, які роблять менше, ніж викликів, щоб робити додаткові фіктивні виклики (тобто дзвінки, де повернене значення не має значення), так що всі контури обчислень здійснюють саме виклики. Будь-яка задана послідовність результатів наших дзвінків до повинна приводити до певної перестановки на виході, і тому ми можемо створити "таблицю результатів", яка відображає будь-яку задану послідовність результатів наших дзвінків у конкретну вихідна перестановка Оскільки кожен із цих результатів однаково вірогідний (кожен з них має ймовірністьk k k k ( r 1 , r 2 , , r k ) 1kRandNum50kkRandNum50kkRandNum50(r1,r2,,rk) С150k ), то ймовірність отримання якоїсь певної перестановки з нашого алгоритму повинна мати форму для деякого . Але Не може бути такої форми, тому щоне ділить для жодного (наприклад, 3 ділить але не може ділити будь-яке число форми ). Це означає, що ніякий можливий розподіл результатів на виклики випадкових чисел не може призвести до рівномірної перестановки. c1c50kc100! 50кk100! 50к1100!100!50kk100!50k


2

nlogn+O(1)

if ( rand50() > 25 ) then b = 1 else b = 0   // random bit

1n!123n

Якщо ви не знаєте, як генерувати уніформу, як це пропонується у цій публікації, з випадкового біта, ви також можете генерувати наближення уніформи таким чином (що еквівалентно "справді", але швидше)

P = (RandNum50()-1) + (RandNum50()-1)*50^1 + (RandNum50()-1)*50^2 + ...

P50Pn = 100 ! P > nQ=Pmodnn=100!P>n


1

Я не робив аналіз, щоб підтвердити, наскільки це було б рівномірним (чи ні), і це могло б бути відрегульовано як справжнє перетасування, але ви могли просто вибрати, починаючи з масиву ith -го індексу = i + 1, (k + RandNum50() + RandNum50() - 1) mod (100 - k)індексу, з видалення, для k= 0..99?

Це "висуває" пік RandNum50() + RandNum50()розподілу вперед рівномірно.

Я впевнений, що це не зовсім правильно, тому що я це заявив, тому що індекс 0 (1) неможливо отримати з першого вибору, і я не можу швидко побачити альтернативне коригування 1..50 + 1..50, яке створює 0 ..99.

Оновлення

Щоб виправити проблему, яку я зазначив, я ефективно використовував, RandNum100як згадується в коментарях до запитання, для випадкової ініціалізації першого kзміщення.

Це виробляє розподіл зі значною хвилею на фронті.

Замість того, щоб просуватися на 1, я використовував інший RandNum50для збільшення цього першого k. Це дає результат, який є досить випадковим для мене, але він все ще не є "справді" випадковим, як це легко видно, якщо змінити K на 2.

Тестування коду VB.NET, де я працював за будь-яким рівним K. Зауважте, що це фактично O (K), 6K + 2.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.