web-dev-qa-db-fra.com

Générer m nombres aléatoires distincts dans l'intervalle [0..n-1]

J'ai deux méthodes pour générer m nombres aléatoires distincts dans l'intervalle [0..n-1]

Méthode 1:

//C++-ish pseudocode
int result[m];
for(i = 0; i < m; ++i)
{
   int r;
   do
   {
      r = Rand()%n;
   }while(r is found in result array at indices from 0 to i)
   result[i] = r;   
}

Méthode 2:

//C++-ish pseudocode
int arr[n];
for(int i = 0; i < n; ++i)
    arr[i] = i;
random_shuffle(arr, arr+n);
result = first m elements in arr;

La première méthode est plus efficace lorsque n est beaucoup plus grande que m, alors que la seconde est plus efficace autrement. Mais "beaucoup plus grand" n'est-ce pas une notion stricte, n'est-ce pas? :) 

Question:Quelle formule de n et m dois-je utiliser pour déterminer si method1 ou method2 sera plus efficace? (en termes d'espérance mathématique du temps d'exécution)

31
Armen Tsirunyan

Consultez la description de Wikipedia de l'algorithme original de Fisher-Yates . Il recommande d'utiliser essentiellement votre méthode 1 pour un maximum de n/2 et votre méthode 2 pour le reste.

9
Mark Ransom

Voici un algorithme qui fonctionnera dans la mémoire O(n) et dans le temps O(n) (où n est le nombre de résultats renvoyés et non la taille de l'ensemble dans lequel vous faites votre choix). ensemble de résultats. C'est en Python pour plus de commodité car il utilise une table de hachage:

def random_elements(num_elements, set_size):
    state = {}
    for i in range(num_elements):
        # Swap state[i] with a random element
        swap_with = random.randint(i, set_size - 1)
        state[i], state[swap_with] = state.get(swap_with, swap_with), state.get(i, i)
    return [state[i] for i in range(num_elements) # effectively state[:num_elements] if it were a list/array.

Il ne s’agit que d’un brassage partiel entre pêcheurs, le tableau étant brassé et mis en œuvre sous forme de hachage clairsemée - tout élément non présent est égal à son index. Nous mélangeons les premiers index num_elements et renvoyons ces valeurs. Dans le cas où set_size = 1, équivaudrait à choisir un nombre aléatoire dans la plage, et dans le cas où num_elements = set_size, cela équivaudrait à un mélange standard de type pêcheur.

Il est trivial d'observer qu'il s'agit de O(n) heure et, comme chaque itération de la boucle initialise au plus deux nouveaux index dans la table de hachage, il s'agit également de O(n) espace.

6
Nick Johnson

Personnellement, je voudrais utiliser la méthode 1, puis si M> N/2, choisissez N-M valeurs, puis inverser le tableau (retourner les nombres qui n'ont pas été choisis). Ainsi, par exemple, si N vaut 1000 et que vous en voulez 950, choisissez 50 valeurs à l'aide de la méthode 1, puis renvoyez les 950 autres.

Edit: Cependant, si votre objectif est d’obtenir des performances constantes, j’utiliserais une méthode 2 modifiée, qui ne permet pas le brassage intégral, mais ne mélange que les M premiers éléments de votre tableau de longueur N. 

int arr[n];
for(int i = 0; i < n; ++i)
    arr[i] = i;

for (int i =0; i < m; ++i) {
   int j = Rand(n-i); // Pick random number from 0 <= r < n-i.  Pick favorite method
   // j == 0 means don't swap, otherwise swap with the element j away
   if (j != 0) { 
      std::swap(arr[i], arr[i+j]);
   }
}
result = first m elements in arr;
6
Dave S

Qu'en est-il d'une troisième méthode?

int result[m];
for(i = 0; i < m; ++i)
{
   int r;
   r = Rand()%(n-i);
   r += (number of items in result <= r)
   result[i] = r;   
}

Edit il devrait être <=. et ce serait en fait une logique supplémentaire pour éviter les collisions.

C’est mieux, un exemple utilisant la Méthode moderne de Fisher-Yates

//C++-ish pseudocode
int arr[n];
for(int i = 0; i < n; ++i)
    arr[i] = i;

for(i = 0; i < m; ++i)
    swap(arr, n-i, Rand()%(n-i) );

result = last m elements in arr;
3
Jacob Eggers

C'est un peu long, mais cela pourrait fonctionner, selon votre système.

  1. Commencez avec un ratio raisonnable, comme 0,5.
  2. Lorsqu'une demande arrive, traitez-la avec la méthode que vous obtenez à partir de la valeur actuelle du rapport de seuil.
  3. Enregistrez le temps que cela prend et quand vous avez du temps "vide", effectuez la même tâche avec l'autre méthode.
  4. Si la solution alternative est beaucoup plus rapide que la solution d'origine, ajustez le seuil vers le haut ou le bas.

Le défaut évident de cette méthode est que, sur les systèmes à charge très variable, votre test "hors ligne" ne sera pas trop fiable.

1
biziclop

Il a été suggéré de mélanger Fisher-Yates. Je ne sais pas si le code suivant génère des nombres entiers équitablement répartis, mais il est au moins compact et à passe unique

std::random_device rd;
std::mt19937 g(rd());
for (size_type i = 1; i < std::size(v); ++i) {
    v[i] = std::exchange(v[g() % i], i);
}
0
Orient

Qu'en est-il de l'utilisation de set au lieu d'un tableau, je pense qu'il est beaucoup plus facile que tableau 

set<int> Numbers;
while (Numbers.size() < m) {
   Numbers.insert(Rand() % n);
}
0
Hani Shams