Algoritmo più efficiente per stampare 1-100 utilizzando un determinato generatore di numeri casuali


11

Ci viene dato un generatore di numeri casuali RandNum50che genera un numero intero casuale uniformemente compreso tra 1 e 50. Possiamo usare solo questo generatore di numeri casuali per generare e stampare tutti gli interi da 1 a 100 in un ordine casuale. Ogni numero deve arrivare esattamente una volta e la probabilità che un numero si verifichi in qualsiasi luogo deve essere uguale.

Qual è l'algoritmo più efficiente per questo?


1
Utilizzare un vettore array / o bit per registrare i numeri già visti e un contatore per registrare il numero di numeri univoci visti.
Dave Clarke,

@DaveClarke Come posso generare un numero maggiore di 50 con quello? Se lo uso più di 1 volta, quindi, come genererò 1 utilizzandoli?
Raj Wadhwa,

1
La sfida ovviamente è garantire che tutti i luoghi si verifichino con uguale probabilità. Puoi usare RandNum100 = (RandNum50() * 2) - (RandNum50 > 25) ? 0 : 1).
Dave Clarke,

2
@DaveClarke: Quindi proponi un campionamento iterato di rifiuto? Ciò terminerebbe solo in previsione.
Raffaello

Stavo solo dando un suggerimento.
Dave Clarke,

Risposte:


3

Ho pensato (quindi può essere sbagliato :-) di questa soluzione che utilizza la shuffle Fisher-Yates . Per mantenere una distribuzione uniforme con una buona approssimazione (vedi la sezione EDIT di seguito) ad ogni iterazione puoi usare questo trucco per produrre un valore tra 0 e k - 1 :O(N2)krand0K-1

 // 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
 }

L'algoritmo Fisher-Yates diventa:

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]

MODIFICARE:

Come sottolineato da Erick, la krandfunzione sopra non restituisce una distribuzione veramente uniforme. Esistono altri metodi che possono essere utilizzati per ottenere un'approssimazione migliore (arbitrariamente migliore) e più veloce; ma (per quanto ne so) l'unico modo per ottenere una distribuzione veramente uniforme è usare il campionamento del rifiuto : pick bit casuali e se il numero r ottenuto è inferiore a k restituiscilo, altrimenti genera un altro numero casuale; una possibile implementazione:m=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
La pagina Wikipedia a cui ti colleghi afferma che esiste una variante . O(n)
Dave Clarke,

1
Penso che "shuffle" sia la parola d'ordine chiave qui.
Raffaello

4
Il trucco in Krand (k) non produce una distribuzione veramente uniforme, anche se è una buona approssimazione: anche per k = 3 questo ha una probabilità del 33,333328% di produrre 0. C'è una giustificazione per sommare fino a k qui ? Penserei che un limite inferiore sia sufficiente se vogliamo solo un'approssimazione.
Erick Wong,

1
@ErickWong: hai ragione; Penso che la vera distribuzione uniforme possa essere raggiunta solo usando il metodo di campionamento del rifiuto che non è garantito per terminare in tempo costante. Esistono altri schemi di approssimazione (che consentono di raggiungere qualsiasi approssimazione desiderata), quello che ho proposto è il primo che mi è venuto in mente.
Vor

2
@ ex0du5: lo so, ma come si produce una permutazione casuale uniforme di numeri [1..100] usando solo un generatore casuale uniforme in [1..100]? L'unico metodo alternativo che conosco è: step1) selezionare un valore casuale in 1..100 ; step2) se r è già stato scelto, scartalo e vai a step1; step3) stampa r ; step4) se non abbiamo stampato tutti i 100 numeri vai al step1. Ma questo metodo sposta semplicemente il rifiuto agli elementi già scelti. r1..100rr
Vor

4

Dato che altre persone hanno dato soluzioni approssimative e soluzioni che implicano l'assunzione di un numero indeterminato di deviati, che ne dite di una prova che non esiste un tale algoritmo che è garantito per richiedere solo un numero finito di RandNum50()chiamate?

Come altri hanno notato, stampare i numeri da 1 a 100 in ordine casuale equivale a stampare una permutazione casuale di questi numeri; ce ne sono 100! di queste permutazioni, e quindi ogni particolare permutazione deve essere emessa con probabilità .1100!

KRandNum50KKRandNum50KKRandNum50(r1,r2,...,rK)150Kc50Kc1100!100!50KK100!50K


2

nlogn+O(1)

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

1n!123n

Se non sai come generare un'uniforme, come suggerito in quel post, da un bit casuale, potresti anche generare direttamente un'approssimazione dell'uniforme, in questo modo (che equivale al "trulyrand" di Vor, ma più veloce):

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

P50PQ=Pmodnn=100!P>n


1

Non ho fatto l'analisi per confermare quanto uniforme (o no) sarebbe, e potrebbe essere regolato per essere un vero shuffle, ma potresti semplicemente scegliere, da una matrice iniziale del ith index = i + 1, l' (k + RandNum50() + RandNum50() - 1) mod (100 - k)indice, con rimozione, per k= 0..99?

Questo "spinge" il picco nella RandNum50() + RandNum50()distribuzione in avanti uniformemente.

Sono abbastanza sicuro che non sia del tutto corretto come ho affermato perché l'indice 0 (1) non è ottenibile dalla prima scelta e non riesco a vedere rapidamente una regolazione alternativa 1..50 + 1..50 che produce 0 ..99.

Aggiornare

Per risolvere il problema che ho notato, ho effettivamente utilizzato RandNum100come indicato nei commenti alle domande per inizializzare in modo casuale il primo koffset.

Questo produce una distribuzione con un'onda significativa nella parte anteriore.

Invece di avanzare di 1 ne ho usato un altro RandNum50per incrementarlo per primo k. Questo produce un risultato abbastanza casuale per me, ma non è ancora "veramente" casuale, come si può facilmente vedere se si cambia K in 2.

Test del codice VB.NET in cui ho provveduto a soddisfare anche qualsiasi K. Nota che in realtà è O (K), 6K + 2.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.