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


11

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

Какой алгоритм для этого наиболее эффективен?


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

@DaveClarke Как я могу сгенерировать число больше 50 с этим? Если я использую его более 1 раза, то также, как я буду генерировать 1, используя их?
Радж Вадхва

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

2
@DaveClarke: То есть вы предлагаете повторную выборку отклонений? Это закончится только в ожидании.
Рафаэль

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

Ответы:


3

Я думал (так что может быть неправильно :-) этого решение , которое использует перетасовать Fisher-Yates . Для того , чтобы поддерживать равномерное распределение с хорошим приближением (смотрите раздел 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
Я думаю, что «shuffle» является ключевым модным словом здесь.
Рафаэль

4
Уловка в krand (k) не дает действительно равномерного распределения, хотя это хорошее приближение: даже при k = 3 это дает 33,333328% шансов на вывод 0. Есть ли здесь обоснование для суммирования до k ? Я думаю, что меньшего предела достаточно, если мы просто хотим приближения.
Эрик Вонг

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

2
@ ex0du5: Я знаю это, но как создать равномерную случайную перестановку чисел [1..100], используя только равномерный генератор случайных чисел в [1..100]? Единственный альтернативный метод, который я знаю, это: step1) выбрать случайное значение в ; step2) если уже выбрано, отбросьте его и переходите к step1; шаг 3) печать ; step4) если мы не напечатали все 100 чисел, перейдите к шагу 1. Но этот метод просто переносит отклонение на уже выбранные элементы. 1..100 r rr1..100rr
Вор

4

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

Как отмечали другие, печать чисел от 1 до 100 в случайном порядке эквивалентна печати случайной перестановки этих чисел; Есть 100! из этих перестановок, и поэтому любая конкретная перестановка должна быть выведена с вероятностью .1100!

Но если бы мы знали, что наш алгоритм использовал не более вызовов для некоторого , то мы могли бы рассуждать следующим образом: во-первых, выделите те вычислительные пути, которые делают меньше вызовов, чтобы сделать дополнительные фиктивные вызовы (то есть вызовы, где возвращаемое значение не имеет значения), так что все пути вычислений делают ровно вызовов. Любая заданная последовательность результатов наших вызовов должна приводить к некоторой выходной перестановке, и поэтому мы можем построить «таблицу результатов», которая отображает любую данную последовательность результатов наших вызовов в конкретный выходная перестановка. Так как каждый из этих результатов одинаково вероятен (каждый из них имеет вероятностьk k k k ( r 1 , r 2 , , r k ) 1kRandNum50kkRandNum50kkRandNum50(r1,r2,,rk) гр150k ), тогда вероятность получения какой-либо конкретной перестановки из нашего алгоритма должна иметь вид для некоторого . Но может быть такой формы, потому чтоне делит для любого (например, 3 делит но не может делить любое число формы ). Это означает, что никакое возможное распределение результатов для вызовов случайных чисел не может привести к равномерной перестановке. с1c50kc100! 50кк100! 50к1100!100!50kk100!50k


2

Предыдущие решения не являются оптимальными. Сложность точно равна в вызовах RandNum50 и подробно описана здесь с использованием в качестве источника случайного бита (как предложено Vor):nlogn+O(1)

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

Основная идея заключается в том, что вы экономите много битов, если генерируете униформу от дои затем, используя факториальную базовую декомпозицию , вместо генерации последовательности униформ в диапазоне до , затем , затем и т. д., . Это на самом деле, как я уже упоминал в посте, тема статьи, которую я представил!п ! 1 2 3 н1n!123n

Если вы не знаете, как сгенерировать униформу, как предлагается в этом посте, из случайного бита, вы также можете сгенерировать аппроксимацию униформы напрямую, таким образом (что эквивалентно «истинно» Вора, но быстрее):

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

идти так далеко, как вам нужно пойти. Это развивается в базе . Тогда просто усеченный , т.е. , в вашем случае, Это значение не является полностью случайным, но это мера однородности, которая часто используется. Или, как говорит Вор, вы можете отказаться , если . Затем с этим значением, вы можете сделать расширение факторных баз , как описаны в посте .50 P Q = PP50PQ=Pmodnn=100!P>n


1

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

Это «толкает» пик в RandNum50() + RandNum50()распределении вперед равномерно.

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

Обновить

Чтобы устранить эту проблему , я отметил, я эффективно использовать RandNum100как уже упоминалось в комментариях вопрос к randomally Инициализируем первого kсмещения.

Это создает распределение со значительной волной на фронте.

Вместо того, чтобы продвигаться на 1, я использовал другой, RandNum50чтобы увеличить это первым k. Это дает достаточно случайный для меня результат, но все же он не является «действительно» случайным, что можно легко увидеть, если изменить K на 2.

Тестирование кода VB.NET где я обслужены любой даже K. Примечание оно представляет собой О (К), 6К + 2 в самом деле.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.