Algoritmo mais eficiente para imprimir 1-100 usando um determinado gerador de números aleatórios

11

Nos é dado um gerador de números aleatórios RandNum50que gera um número inteiro aleatório uniformemente no intervalo de 1 a 50. Podemos usar apenas esse gerador de números aleatórios para gerar e imprimir todos os números inteiros de 1 a 100 em uma ordem aleatória. Todo número deve vir exatamente uma vez, e a probabilidade de qualquer número ocorrer em qualquer lugar deve ser igual.

Qual é o algoritmo mais eficiente para isso?

Raj Wadhwa
fonte
1
Use um vetor de matriz / ou bit para registrar os números já vistos e um contador para registrar o número de números únicos vistos.
Dave Clarke
@DaveClarke Como posso gerar um número maior que 50 com isso? Se eu usá-lo mais de uma vez, também como gerarei 1 usando-os?
Raj Wadhwa
1
O desafio, é claro, é garantir que todos os locais ocorram com a mesma probabilidade. Você poderia usar RandNum100 = (RandNum50() * 2) - (RandNum50 > 25) ? 0 : 1).
Dave Clarke
2
@DaveClarke: Então você propõe amostragem de rejeição iterada? Isso terminaria apenas na expectativa.
Raphael
Eu estava apenas dando uma dica.
Dave Clarke

Respostas:

3

Pensei (para que possa estar errado :-) nesta solução que usa o shuffle de Fisher-Yates . Para manter uma distribuição uniforme com boa aproximação (consulte a seção EDIT abaixo) a cada iteração, você pode usar este truque para produzir um valor entre e :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
 }

O algoritmo de Fisher-Yates se torna:

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]

EDITAR:

Como apontado por Erick, a krandfunção acima não retorna uma distribuição verdadeiramente uniforme. Existem outros métodos que podem ser usados ​​para obter uma aproximação melhor (arbitrariamente melhor) e mais rápida; mas (até onde ), a única maneira de obter uma distribuição verdadeiramente uniforme é usar a amostragem por rejeição : escolha bits aleatórios e se o número obtido for menor que devolva-o, caso contrário, gere outro número aleatório; uma possível implementação: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
    }
}
Vor
fonte
1
A página da wikipedia à qual você vincula afirma que existe uma variante . O(n)
Dave Clarke
1
Eu acho que "shuffle" é a palavra chave aqui.
Raphael
4
O truque no krand (k) não produz uma distribuição verdadeiramente uniforme, embora seja uma boa aproximação: mesmo para k = 3, isso oferece uma probabilidade de 33,333328% de gerar 0. Existe uma justificativa para resumir até k aqui ? Eu acho que um limite menor será suficiente se quisermos apenas uma aproximação.
Erick Wong
1
@ ErickWong: você está certo; Penso que a verdadeira distribuição uniforme só pode ser alcançada usando o método de amostragem por rejeição, que não garante que termine em tempo constante. Existem outros esquemas de aproximação (que permitem alcançar qualquer aproximação desejada), o que propus é o primeiro que me veio à cabeça.
Vor
2
@ ex0du5: Eu sei disso, mas como você produz uma permutação aleatória uniforme de números [1..100] usando apenas um gerador aleatório uniforme em [1..100]? O único método alternativo que eu conheço é: passo1) escolha um valor aleatório em ; passo 2) se já foi escolhido, descarte-o e vá para o passo 1; passo 3) imprimir ; step4) se não tivermos impresso todos os 100 números, vá para a step1. Mas esse método simplesmente muda a rejeição para os elementos já selecionados. 1..100 r rr1..100rr
Vor
4

Como outras pessoas deram soluções aproximadas e envolvem a obtenção de números indeterminados de desvios, que tal uma prova de que não existe tal algoritmo que garanta apenas um número finito de RandNum50()chamadas?

Como outros observaram, imprimir os números de 1 a 100 em ordem aleatória é equivalente a imprimir uma permutação aleatória desses números; existem 100! dessas permutações e, portanto, qualquer permutação específica deve ser gerada com probabilidade .1100!

Mas se soubéssemos que nosso algoritmo usava no máximo chamadas para alguns , poderíamos argumentar da seguinte maneira: primeiro, cubra os caminhos de computação que fazem menos de chamadas para fazer chamadas fictícias adicionais (ou seja, chamadas onde o valor retornado é irrelevante), de modo que todos os caminhos de computação fazem exatamente chamadas. Qualquer sequência de resultante de nossas chamadas para deve resultar em alguma permutação de saída e, portanto, podemos criar uma 'tabela de resultados' que mapeia qualquer sequência de resultados de nossas chamadas em um determinado permutação de saída. Como cada um desses resultados é igualmente provável (cada um deles tem probabilidadek k k k ( r 1 , r 2 , , r k ) 1kRandNum50kkRandNum50kkRandNum50(r1,r2,,rk) c150k ), então a probabilidade de obter qualquer permutação específica de nosso algoritmo deve estar no formato para alguns . Mas Não pode ser desta forma, porquenão divide por nenhum (por exemplo, 3 divide mas não pode dividir nenhum número da forma ). Isso significa que nenhuma distribuição possível de resultados para chamadas com números aleatórios pode produzir uma permutação uniforme. c1c50kc100! 50kk1100!100!50kk50 k100!50k

Steven Stadnicki
fonte
2

As soluções anteriores não são ótimas. A complexidade é exatamente nas chamadas para RandNum50 e é descrita em alguns detalhes aqui , usando como fonte de bit aleatório (como sugerido por Vor):nlogn+O(1)

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

A idéia básica é que você salve muitos bits se gerar um uniforme entre e, E, em seguida, utilizando a decomposição de base fatorial , em vez de gerar uma sequência de uniformes variou-se a , em seguida, , então , etc., . Este é realmente, como mencionei no post, o tópico de um artigo que enviei!n ! 1 2 3 n1n!123n

Se você não souber como gerar um uniforme, como sugerido nesse post, a partir de um bit aleatório, também poderá gerar uma aproximação do uniforme diretamente, desta maneira (o que é equivalente ao "trulyrand" de Vor, mas mais rápido):

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

indo o mais longe que você precisa. Isso está desenvolvendo na base . Em seguida, basta truncar , ou seja, , no seu caso. Esse valor não é completamente aleatório, mas é uma medida de uniformidade que é frequentemente usada. Ou, como Vor sugere, você pode rejeitar se . Então, com esse valor, você pode fazer a expansão da base fatorial, conforme descrito na publicação .50 PP50Pn = 100 ! P > nQ=Pmodnn=100!P>n

Jérémie
fonte
1

Eu não fiz a análise para confirmar o quão uniforme (ou não) isso seria, e poderia ser ajustado para ser um verdadeiro embaralhamento, mas você poderia escolher, a partir de uma matriz inicial do ith index = i + 1, o (k + RandNum50() + RandNum50() - 1) mod (100 - k)índice, com remoção, para k= 0..99?

Isso "empurra" o pico da RandNum50() + RandNum50()distribuição adiante uniformemente.

Tenho certeza de que isso não está certo, como afirmei, porque o índice 0 (1) não pode ser obtido desde a primeira escolha e não consigo ver rapidamente um ajuste alternativo 1..50 + 1..50 que produz 0 ..99.

Atualizar

Para corrigir o problema que observei, usei efetivamente RandNum100conforme mencionado nos comentários da pergunta para inicializar aleatoriamente o primeiro kdeslocamento.

Isso produz uma distribuição com uma onda significativa na frente.

Em vez de avançar 1, usei outro RandNum50para incrementar isso primeiro k. Isso produz um resultado que é aleatório o suficiente para mim, mas ainda não é "verdadeiramente" aleatório, como pode ser facilmente visto se você alterar K para 2.

Testando o código VB.NET, onde eu atendi qualquer K. par. Note que é O (K), 6K + 2, de fato.

Mark Hurd
fonte