La génération m distinctes nombres aléatoires dans l'intervalle [0..n-1]
J'ai deux méthodes de génération de m distinctes nombres aléatoires 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 grand que m, tandis que la seconde est plus efficace autrement. Mais "beaucoup plus grand" n'est pas stricte de la notion, est-il? 🙂
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 de la durée de fonctionnement)
- Si
m
est vraiment petit, n'efficacité tant d'importance? Optimiser pour le cas qui est plus susceptible de causer des problèmes. - Je vais dynamiquement obtenir n, et m. Je vais avoir à déterminer d'exécution de la méthode à utiliser
- Avez-vous testé les deux méthodes avec un couple de différents paramètres? Juste pour avoir une sensation approximative de combien de temps ils prennent.
Vous devez vous connecter pour publier un commentaire.
Mathématiques pures:
Nous allons calculer la quantité de
rand()
appels de fonction dans les deux cas, et de comparer les résultats:Cas 1:
nous allons voir l'espérance mathématique d'appels sur l'étape
i = k
, quand vous avez déjà k numéros choisis. La probabilité d'obtenir un nombre avec unrand()
appel est égal àp = (n-k)/n
. Nous avons besoin de savoir l'espérance mathématique de tels appels à la quantité qui conduit à l'obtention d'un numéro nous n'avons pas encore.La probabilité d'obtenir de l'aide
1
appel estp
. À l'aide de2
appels -q * p
, oùq = 1 - p
. Dans le cas général, la probabilité d'obtenir exactement aprèsn
appels est(q^(n-1))*p
. Ainsi, l'espérance mathématique estSum[ n * q^(n-1) * p ], n = 1 --> INF
. Cette somme est égale à1/p
(prouvé par wolfram alpha).Ainsi, à l'étape
i = k
vous allez effectuer1/p = n/(n-k)
appels de larand()
fonction.Maintenant, nous allons somme globale de:
Sum[ n/(n - k) ], k = 0 --> m - 1 = n * T
- le nombre derand
appels de la méthode 1.Ici
T = Sum[ 1/(n - k) ], k = 0 --> m - 1
Cas 2:
Ici
rand()
est appelée à l'intérieur d'random_shuffle
n - 1
fois (dans la plupart des implémentations).Maintenant, le choix de la méthode, il nous faut comparer ces deux valeurs:
n * T ? n - 1
.Donc, pour choisir la méthode appropriée, calculer
T
comme décrit ci-dessus. SiT < (n - 1)/n
il est préférable d'utiliser la première méthode. Utiliser la deuxième méthode contraire.Vérifier la Wikipédia description de la origine de Fisher-Yates algorithme. Il prône une utilisation essentiellement de votre méthode 1 jusqu'à n/2, et votre méthode 2 pour le reste.
m
valeurs.Personnellement, je voudrais utiliser la Méthode 1, et puis si M > N/2, choisissez N-M valeurs, puis inverser la matrice (retourner les numéros qui n'ont pas été pris). Ainsi, par exemple, si N est de 1000 et que vous voulez 950 d'entre eux, choisi 50 valeurs à l'aide de la Méthode 1, puis les retourner et les autres 950.
Edit: Si, si cohérente de la performance est votre but, je voudrais utiliser une version modifiée de la méthode 2, ce qui ne veut pas faire le plein de lecture aléatoire, mais seulement le mélange de la première M éléments de votre N longueur du tableau.
Voici un algorithme qui fonctionne en temps O(n) de la mémoire et de O(n) temps (où n est le nombre de résultats retournés, pas de la taille de l'ensemble que vous êtes en sélectionnant à partir de) pour n'importe quel jeu de résultats. C'est en Python pour des raisons de commodité, car il utilise une table de hachage:
C'est juste une partielle shuffle de fisher-yates, avec la matrice été battues en œuvre comme un éparses à la table de hachage tout élément qui n'est pas présent est égal à son index. Nous aléatoire de la première
num_elements
indices, et le retour de ces valeurs. Dans le cas quiset_size = 1,
c'est l'équivalent de choisir un nombre aléatoire dans l'intervalle, et dans le cas quinum_elements = set_size
, c'est équivalent à une norme de fisher-yates shuffle.Il est trivial de constater que ce est O(n) le temps, et parce que chaque itération de la boucle initialise au plus deux nouveaux indices dans la table de hachage, il est O(n) dans l'espace, trop.
swap_with = random.randint(i, set_size-1)
depuis randint() utilise un large éventail? @nick-johnsonQu'environ un tiers de la méthode?
Modifier il convient de <=. et il serait effectivement logique supplémentaire pour éviter les collisions.
C'est mieux, un exemple d'utilisation de la Méthode Moderne de Fisher-Yates
(number of items in result < r)
? Si cela veut dire que 1 est ajouté si la r est plus grand que le nombre d'éléments du résultat?(number of items in result < r)
vérifier efficacement.rand() to n-i
[0,n-1]
, puis choisir aléatoirement une de lan-1
reste...enfin choisir l'un de l'n-m+1
restants. C'est le début de la shuffle de Fisher-Yates, mais en s'arrêtant à n-m au lieu de 2.Parler de l'espérance mathématique, c'est assez inutile, mais je post quand même 😀
Shuffle est simple O(m).
Maintenant l'algorithme est un peu plus complexe. Le nombre d'étapes nécessaires pour générer le prochain numéro de la valeur attendue du nombre d'essais, et la probabilité de la durée du procès est un geomtric de distribution. Alors...
Noter que la somme peut être divisé en une forme de triangle, voir du côté droit.
Nous allons utiliser la formule de la série harmonique: H_n = Somme de k=0->n (1/k) = env ln(k)
Et il y a quelques forumla pour la somme de la série harmonique, si vous êtes encore intéressé, je vais regarder...
Mise à jour: en fait, c'est assez agréable de formule (grâce à la brillante Béton livre de Mathématiques)
De sorte que le nombre d'étapes:
Remarque: je n'ai pas vérifié.
C'est un peu un long shot, mais il pourrait fonctionner, en fonction de votre système.
Le défaut évident de cette méthode est que, très variable, les systèmes de charge à votre "hors ligne" test ne sera pas trop fiable.
Il a été suggéré de Fisher-Yates shuffle. Ne sais pas si le code suivant génère également distribué des entiers, mais il est au moins compact et une passe:
Que sur l'utilisation de ensemble au lieu de tableau, je pense qu'il est beaucoup plus facile que la matrice de
Tout à fait peut-être qu'il serait plus simple de le faire démarrer en mode debug (et de garder une méthode comme une note) pour un couple de fois pour obtenir une moyenne, puis d'utiliser l'autre méthode pour obtenir une moyenne à partir de ce
Je ne conseille pas cette méthode, mais elle fonctionne