Algorithme le plus efficace pour imprimer 1-100 à l'aide d'un générateur de nombres aléatoires donné

11

On nous donne un générateur de nombres aléatoires RandNum50qui génère uniformément un entier aléatoire compris entre 1 et 50. Nous pouvons utiliser uniquement ce générateur de nombres aléatoires pour générer et imprimer tous les entiers de 1 à 100 dans un ordre aléatoire. Chaque nombre doit arriver exactement une fois, et la probabilité qu'un nombre se produise à n'importe quel endroit doit être égale.

Quel est l'algorithme le plus efficace pour cela?

Raj Wadhwa
la source
1
Utilisez un tableau / ou un vecteur de bits pour enregistrer les nombres déjà vus et un compteur pour enregistrer le nombre de nombres uniques vus.
Dave Clarke
@DaveClarke Comment puis-je générer un nombre supérieur à 50 avec ça? Si je l'utilise plus d'une fois, comment puis-je en générer 1 en les utilisant?
Raj Wadhwa
1
Le défi est bien sûr de garantir que tous les endroits se produisent avec la même probabilité. Vous pourriez utiliser RandNum100 = (RandNum50() * 2) - (RandNum50 > 25) ? 0 : 1).
Dave Clarke
2
@DaveClarke: Vous proposez donc un échantillonnage de rejet itéré? Cela ne se terminerait que dans l'attente.
Raphael
Je donnais simplement un indice.
Dave Clarke

Réponses:

3

J'ai pensé (donc ça peut être faux :-) de cette solution qui utilise le shuffle de Fisher-Yates . Afin de conserver une distribution uniforme avec une bonne approximation (voir la section EDITER ci-dessous) à chaque itération, vous pouvez utiliser cette astuce pour produire une valeur entre 0 et k - 1 :O(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
 }

L'algorithme de Fisher-Yates devient:

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]

ÉDITER:

Comme l'a souligné Erick, la krandfonction ci-dessus ne renvoie pas une distribution vraiment uniforme. Il existe d'autres méthodes qui peuvent être utilisées pour obtenir une meilleure approximation (arbitrairement meilleure) et plus rapide; mais (à ma connaissance) la seule façon d'obtenir une distribution vraiment uniforme est d'utiliser l' échantillonnage de rejet : choisir bits aléatoires et si le nombre r obtenu est inférieur à k le retourner, sinon générer un autre nombre aléatoire; une mise en œuvre possible: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
    }
}
Vor
la source
1
La page wikipedia à laquelle vous accédez indique qu'il existe une variante . O(n)
Dave Clarke
1
Je pense que "shuffle" est le mot clé ici.
Raphael
4
L'astuce dans krand (k) ne produit pas une distribution vraiment uniforme, bien que ce soit une bonne approximation: même pour k = 3, cela donne 33,333328% de chances de sortir 0. Y a-t-il une justification pour sommer jusqu'à k ici ? Je pense qu'une limite plus petite suffit si nous voulons juste une approximation.
Erick Wong
1
@ErickWong: vous avez raison; Je pense que la vraie distribution uniforme ne peut être atteinte qu'en utilisant la méthode d'échantillonnage par rejet qui ne se termine pas en temps constant. Il existe d'autres schémas d'approximation (qui permettent d'atteindre n'importe quelle approximation souhaitée), celui que j'ai proposé est le premier qui m'est venu à l'esprit.
Vor
2
@ ex0du5: Je le sais, mais comment produire une permutation aléatoire uniforme des nombres [1..100] en utilisant uniquement un générateur aléatoire uniforme dans [1..100]? La seule méthode alternative que je connaisse est: étape1) choisir une valeur aléatoire dans 1..100 ; étape 2) si r a déjà été choisi, jetez-le et passez à l'étape 1; étape 3) imprimer r ; step4) si nous n'avons pas imprimé tous les 100 numéros goto step1. Mais cette méthode déplace simplement le rejet vers les éléments déjà sélectionnés. r1..100rr
Vor
4

Étant donné que d'autres personnes ont donné des solutions approximatives et des solutions impliquant la prise d'un nombre indéterminé de déviations, que diriez-vous d'une preuve qu'il n'y a pas un tel algorithme qui ne garantisse de ne nécessiter qu'un nombre fini d' RandNum50()appels?

Comme d'autres l'ont noté, l'impression des nombres de 1 à 100 dans un ordre aléatoire équivaut à l'impression d'une permutation aléatoire de ces nombres; il y en a 100! de ces permutations, et donc toute permutation particulière doit être sortie avec une probabilité .1100!

Mais si nous savions que notre algorithme utilisait au plus appels pour certains k , alors nous pourrions argumenter comme suit: premièrement, supprimez les chemins de calcul qui font moins de k appels pour faire des appels fictifs supplémentaires (c'est-à-dire des appels où le la valeur retournée n'est pas pertinente), de sorte que tous les chemins de calcul effectuent précisément k appels. Toute séquence donnée de k résultats de nos appels à doit entraîner une certaine permutation de sortie, et nous pouvons donc construire une «table de résultats» qui mappe toute séquence donnée ( r 1 , r 2 , , r k )kRandNum50kkRandNum50kkRandNum50(r1,r2,,rk)des résultats de nos appels dans une permutation de sortie particulière. Étant donné que chacun de ces résultats est également probable (chacun a la probabilité ), alors la probabilité d'obtenir une permutation particulière de notre algorithme doit être de la formec150k pour certainsc. Mais1c50kcne peut pas être de cette forme, car100! ne divise pas50kpour n'importe quelk(par exemple, 3 divise100!mais ne peut diviser aucun nombre de la forme50k). Cela signifie qu'aucune distribution possible des résultats aux appels de nombres aléatoires ne peut produire une permutation uniforme.1100!100!50kk100!50k

Steven Stadnicki
la source
2

nlogn+O(1)

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

1n!123n

Si vous ne savez pas comment générer un uniforme, comme suggéré dans ce post, à partir d'un bit aléatoire, vous pouvez également générer une approximation de l'uniforme directement, de cette manière (qui est équivalente au "trulyrand" de Vor, mais plus rapide):

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

P50PQ=Pmodnn=100!P>n

Jérémie
la source
1

Je n'ai pas fait l'analyse pour confirmer à quel point cela serait uniforme (ou non), et cela pourrait être ajusté pour être un vrai mélange, mais pourriez-vous simplement choisir, à partir d'un tableau de départ du ith index = i + 1, l' (k + RandNum50() + RandNum50() - 1) mod (100 - k)index, avec suppression, pour k= 0..99?

Cela "pousse" RandNum50() + RandNum50()uniformément le pic de la distribution vers l'avant.

Je suis à peu près sûr que ce n'est pas tout à fait raison, comme je l'ai dit, car l'index 0 (1) ne peut pas être obtenu à partir du premier choix et je ne peux pas voir rapidement un autre ajustement 1..50 + 1..50 qui produit 0 ..99.

Mise à jour

Pour résoudre le problème que j'ai noté, j'ai effectivement utilisé RandNum100comme mentionné dans les commentaires de la question pour initialiser au hasard le premier kdécalage.

Cela produit une distribution avec une onde importante à l'avant.

Au lieu d'avancer de 1, j'en ai utilisé un autre RandNum50pour l'incrémenter en premier k. Cela produit un résultat qui est assez aléatoire pour moi, mais ce n'est toujours pas "vraiment" aléatoire, comme on peut le voir facilement si vous changez K en 2.

Test du code VB.NET où je répondais à tout K. pair. Notez que c'est O (K), 6K + 2 en fait.

Mark Hurd
la source