Je voudrais générer des nombres aléatoires uniques entre 0 et 1000 qui ne se répètent jamais (c'est-à-dire que 6 n'apparaissent pas deux fois), mais cela ne prend pas la forme d'une recherche comme O(N) fais le. Est-ce possible?
Initialisez un tableau de 1001 entiers avec les valeurs 0-1000 et définissez une variable, max, sur l'index max actuel du tableau (en commençant par 1000). Choisissez un nombre aléatoire, r, entre 0 et max, permutez le nombre à la position r avec le nombre à la position max et renvoyez le nombre maintenant à la position max. Décrémentez max de 1 et continuez. Lorsque la valeur maximale est 0, définissez de nouveau la taille maximale du tableau - 1 et recommencez sans avoir à réinitialiser le tableau.
Mise à jour: Bien que j'aie proposé cette méthode seule en répondant à la question, je réalise après quelques recherches qu'il s'agit d'une version modifiée de Fisher-Yates connu sous le nom de Durstenfeld-Fisher-Yates ou Knuth-Fisher-Yates. Puisque la description peut être un peu difficile à suivre, j'ai fourni un exemple ci-dessous (en utilisant 11 éléments au lieu de 1001):
Le tableau commence avec 11 éléments initialisés dans le tableau [n] = n, max commence à 10:
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
+--+--+--+--+--+--+--+--+--+--+--+
^
max
A chaque itération, un nombre aléatoire r est sélectionné entre 0 et max, tableau [r] et tableau [max] sont permutés, le nouveau tableau [max] est renvoyé et max est décrémenté:
max = 10, r = 3
+--------------------+
v v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 7| 8| 9| 3|
+--+--+--+--+--+--+--+--+--+--+--+
max = 9, r = 7
+-----+
v v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 9| 8| 7: 3|
+--+--+--+--+--+--+--+--+--+--+--+
max = 8, r = 1
+--------------------+
v v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 5| 6| 9| 1: 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+
max = 7, r = 5
+-----+
v v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 9| 6| 5: 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+
...
Après 11 itérations, tous les nombres du tableau ont été sélectionnés, max == 0, et les éléments du tableau sont mélangés:
+--+--+--+--+--+--+--+--+--+--+--+
| 4|10| 8| 6| 2| 0| 9| 5| 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+
À ce stade, max peut être réinitialisé à 10 et le processus peut continuer.
Tu peux le faire:
Donc, cela ne nécessite pas une recherche d'anciennes valeurs à chaque fois, mais il faut tout de même O(N) pour le mélange initial. Mais comme Nils l’a souligné dans ses commentaires, cette somme est amortie à O (1).
Utilisez un registre à décalage rétroaction linéaire maximal .
Il est implémentable dans quelques lignes de C et au moment de l'exécution, il ne fait guère plus que quelques tests/branches, un petit ajout et un décalage minime. Ce n'est pas aléatoire, mais cela trompe la plupart des gens.
Vous pouvez utiliser A Linear Congruential Generator . Où m
(le module) serait le nombre le plus proche supérieur à 1000. Lorsque vous obtenez un nombre en dehors de la plage, obtenez le prochain. La séquence ne se répète qu'une fois que tous les éléments ont eu lieu et vous n'avez pas à utiliser de tableau. Soyez conscient des inconvénients de ce générateur cependant (y compris le manque de caractère aléatoire).
Vous pouvez utiliser Format-Preserving Encryption pour chiffrer un compteur. Votre compteur va simplement à partir de 0, et le cryptage utilise une clé de votre choix pour le transformer en une valeur apparemment aléatoire de la base et de la largeur souhaitées. Par exemple. pour l'exemple dans cette question: radix 10, width 3.
Les chiffrements de blocs ont normalement une taille de bloc fixe, par exemple. 64 ou 128 bits. Mais le chiffrement préservant le format vous permet de prendre un chiffrement standard comme AES et de créer un chiffrement de largeur plus petite, quelle que soit la base et la largeur souhaitées, avec un algorithme toujours robuste sur le plan cryptographique.
Il est garanti qu’il n’ya jamais de collisions (car les algorithmes de chiffrement créent un mappage 1: 1). Il est également réversible (mappage bidirectionnel), vous pouvez donc prendre le nombre obtenu et revenir à la valeur de compteur avec laquelle vous avez commencé.
Cette technique n'a pas besoin de mémoire pour stocker un tableau mélangé, etc., ce qui peut être un avantage sur les systèmes avec une mémoire limitée.
AES-FFX est une méthode standard proposée pour y parvenir. J'ai expérimenté du code Python de base basé sur l'idée AES-FFX, bien qu'il ne soit pas totalement conforme - voir code Python ici . Il peut par exemple chiffrer un compteur en un nombre décimal de 7 chiffres ou en 16 bits d'apparence aléatoire. Voici un exemple de radix 10, largeur 3 (pour donner un nombre compris entre 0 et 999 inclus), comme indiqué dans la question:
000 733
001 374
002 882
003 684
004 593
005 578
006 233
007 811
008 072
009 337
010 119
011 103
012 797
013 257
014 932
015 433
... ...
Pour obtenir différentes séquences pseudo-aléatoires non répétitives, modifiez la clé de cryptage. Chaque clé de chiffrement produit une séquence pseudo-aléatoire non répétitive différente.
Pour les nombres faibles comme 0 ... 1000, créer une liste contenant tous les chiffres et la mélanger est simple. Mais si le nombre de chiffres à partir duquel dessiner est très grand, il existe un autre moyen élégant: vous pouvez créer une permutation pseudo-aléatoire en utilisant une clé et une fonction de hachage cryptographique. Consultez le pseudo-code suivant pour l'exemple C++-ish:
unsigned randperm(string key, unsigned bits, unsigned index) {
unsigned half1 = bits / 2;
unsigned half2 = (bits+1) / 2;
unsigned mask1 = (1 << half1) - 1;
unsigned mask2 = (1 << half2) - 1;
for (int round=0; round<5; ++round) {
unsigned temp = (index >> half1);
temp = (temp << 4) + round;
index ^= hash( key + "/" + int2str(temp) ) & mask1;
index = ((index & mask2) << half1) | ((index >> half2) & mask1);
}
return index;
}
Ici, hash
est juste une fonction pseudo aléatoire arbitraire qui mappe une chaîne de caractères sur un nombre entier potentiellement énorme non signé. La fonction randperm
est une permutation de tous les nombres compris entre 0 ... pow (2, bits) -1 en supposant une clé fixe. Cela découle de la construction car chaque étape qui modifie la variable index
est réversible. Ceci est inspiré par un Chiffre de Feistel .
Vous pouvez utiliser mon algorithme Xincrol décrit ici:
http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html
Il s'agit d'une méthode purement algorithmique permettant de générer des nombres aléatoires mais uniques sans tableaux, listes, permutations ni charges de calcul élevées.
La dernière version permet également de définir la plage de nombres. Par exemple, si je veux des nombres aléatoires uniques dans la plage de 0-1073741821.
Je l'ai pratiquement utilisé pour
C'est ouvert, gratuit. Essaie...
Vous n'avez même pas besoin d'un tableau pour résoudre celui-ci.
Vous avez besoin d'un masque de bits et d'un compteur.
Initialisez le compteur à zéro et augmentez-le lors d'appels successifs. XOR le compteur avec le masque binaire (sélectionné au hasard au démarrage ou fixé) pour générer un nombre aléatoire. Si vous ne pouvez pas avoir un nombre supérieur à 1000, n'utilisez pas de masque de traitement plus large que 9 bits. (En d'autres termes, le masque de bits est un entier non supérieur à 511.)
Assurez-vous que lorsque le compteur dépasse 1000, vous le remettez à zéro. À ce stade, vous pouvez sélectionner un autre masque binaire aléatoire, si vous le souhaitez, pour produire le même ensemble de nombres dans un ordre différent.
Voici un code que j'ai tapé et qui utilise la logique de la première solution. Je sais que c'est "agnostique", mais je voulais juste présenter ceci à titre d'exemple en C # au cas où quelqu'un chercherait une solution rapide et pratique.
// Initialize variables
Random RandomClass = new Random();
int RandArrayNum;
int MaxNumber = 10;
int LastNumInArray;
int PickedNumInArray;
int[] OrderedArray = new int[MaxNumber]; // Ordered Array - set
int[] ShuffledArray = new int[MaxNumber]; // Shuffled Array - not set
// Populate the Ordered Array
for (int i = 0; i < MaxNumber; i++)
{
OrderedArray[i] = i;
listBox1.Items.Add(OrderedArray[i]);
}
// Execute the Shuffle
for (int i = MaxNumber - 1; i > 0; i--)
{
RandArrayNum = RandomClass.Next(i + 1); // Save random #
ShuffledArray[i] = OrderedArray[RandArrayNum]; // Populting the array in reverse
LastNumInArray = OrderedArray[i]; // Save Last Number in Test array
PickedNumInArray = OrderedArray[RandArrayNum]; // Save Picked Random #
OrderedArray[i] = PickedNumInArray; // The number is now moved to the back end
OrderedArray[RandArrayNum] = LastNumInArray; // The picked number is moved into position
}
for (int i = 0; i < MaxNumber; i++)
{
listBox2.Items.Add(ShuffledArray[i]);
}
Cette méthode est appropriée lorsque la limite est élevée et que vous souhaitez uniquement générer quelques nombres aléatoires.
#!/usr/bin/Perl
($top, $n) = @ARGV; # generate $n integer numbers in [0, $top)
$last = -1;
for $i (0 .. $n-1) {
$range = $top - $n + $i - $last;
$r = 1 - Rand(1.0)**(1 / ($n - $i));
$last += int($r * $range + 1);
print "$last ($r)\n";
}
Notez que les nombres sont générés dans l'ordre croissant, mais vous pouvez les mélanger ensuite.
Vous pouvez utiliser un bon générateur de nombres pseudo-aléatoires avec 10 bits et jeter 1001 à 1023 en laissant 0 à 1000.
De ici nous obtenons le design pour un PRNG 10 bits ..
10 bits, polynôme de retour x ^ 10 + x ^ 7 + 1 (période 1023)
utiliser un LFSR de Galois pour obtenir un code rapide
Je pense que Générateur congruentiel linéaire serait la solution la plus simple.
et il n'y a que 3 restrictions sur les valeurs a, c et m
PS la méthode a déjà été mentionnée, mais l'article contient de fausses hypothèses sur les valeurs constantes. Les constantes ci-dessous devraient bien fonctionner pour votre cas
Dans votre cas, vous pouvez utiliser a = 1002
, c = 757
, m = 1001
X = (1002 * X + 757) mod 1001
public static int[] randN(int n, int min, int max)
{
if (max <= min)
throw new ArgumentException("Max need to be greater than Min");
if (max - min < n)
throw new ArgumentException("Range needs to be longer than N");
var r = new Random();
HashSet<int> set = new HashSet<int>();
while (set.Count < n)
{
var i = r.Next(max - min) + min;
if (!set.Contains(i))
set.Add(i);
}
return set.ToArray();
}
N Les nombres aléatoires non répétitifs auront une complexité de O(n), selon les besoins.
Remarque: Aléatoire doit être statique avec la sécurité du fil appliquée.
Supposons que vous souhaitiez vérifier plusieurs fois les listes mélangées, sans avoir le délai O(n)
chaque fois que vous recommencez à le mélanger à nouveau. Dans ce cas, nous pouvons le faire:
Créer 2 listes A et B, avec 0 à 1000, prend 2n
espace.
La liste de mélange A utilisant Fisher-Yates prend n
temps.
Lorsque vous tracez un numéro, faites un mélange en une étape de Fisher-Yates sur l’autre liste.
Lorsque le curseur est en fin de liste, passez à l’autre liste.
Prétraitement
cursor = 0
selector = A
other = B
shuffle(A)
Dessiner
temp = selector[cursor]
swap(other[cursor], other[random])
if cursor == N
then swap(selector, other); cursor = 0
else cursor = cursor + 1
return temp
Voici un exemple de code COBOL avec lequel vous pouvez jouer.
Je peux vous envoyer le fichier RANDGEN.exe afin que vous puissiez jouer avec pour voir s’il le veut.
IDENTIFICATION DIVISION.
PROGRAM-ID. RANDGEN as "ConsoleApplication2.RANDGEN".
AUTHOR. Myron D Denson.
DATE-COMPILED.
* **************************************************************
* SUBROUTINE TO GENERATE RANDOM NUMBERS THAT ARE GREATER THAN
* ZERO AND LESS OR EQUAL TO THE RANDOM NUMBERS NEEDED WITH NO
* DUPLICATIONS. (CALL "RANDGEN" USING RANDGEN-AREA.)
*
* CALLING PROGRAM MUST HAVE A COMPARABLE LINKAGE SECTION
* AND SET 3 VARIABLES PRIOR TO THE FIRST CALL IN RANDGEN-AREA
*
* FORMULA CYCLES THROUGH EVERY NUMBER OF 2X2 ONLY ONCE.
* RANDOM-NUMBERS FROM 1 TO RANDOM-NUMBERS-NEEDED ARE CREATED
* AND PASSED BACK TO YOU.
*
* RULES TO USE RANDGEN:
*
* RANDOM-NUMBERS-NEEDED > ZERO
*
* COUNT-OF-ACCESSES MUST = ZERO FIRST TIME CALLED.
*
* RANDOM-NUMBER = ZERO, WILL BUILD A SEED FOR YOU
* WHEN COUNT-OF-ACCESSES IS ALSO = 0
*
* RANDOM-NUMBER NOT = ZERO, WILL BE NEXT SEED FOR RANDGEN
* (RANDOM-NUMBER MUST BE <= RANDOM-NUMBERS-NEEDED)
*
* YOU CAN PASS RANDGEN YOUR OWN RANDOM-NUMBER SEED
* THE FIRST TIME YOU USE RANDGEN.
*
* BY PLACING A NUMBER IN RANDOM-NUMBER FIELD
* THAT FOLLOWES THESE SIMPLE RULES:
* IF COUNT-OF-ACCESSES = ZERO AND
* RANDOM-NUMBER > ZERO AND
* RANDOM-NUMBER <= RANDOM-NUMBERS-NEEDED
*
* YOU CAN LET RANDGEN BUILD A SEED FOR YOU
*
* THAT FOLLOWES THESE SIMPLE RULES:
* IF COUNT-OF-ACCESSES = ZERO AND
* RANDOM-NUMBER = ZERO AND
* RANDOM-NUMBER-NEEDED > ZERO
*
* TO INSURING A DIFFERENT PATTERN OF RANDOM NUMBERS
* A LOW-RANGE AND HIGH-RANGE IS USED TO BUILD
* RANDOM NUMBERS.
* COMPUTE LOW-RANGE =
* ((SECONDS * HOURS * MINUTES * MS) / 3).
* A HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE
* AFTER RANDOM-NUMBER-BUILT IS CREATED
* AND IS BETWEEN LOW AND HIGH RANGE
* RANDUM-NUMBER = RANDOM-NUMBER-BUILT - LOW-RANGE
*
* **************************************************************
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
DATA DIVISION.
FILE SECTION.
WORKING-STORAGE SECTION.
01 WORK-AREA.
05 X2-POWER PIC 9 VALUE 2.
05 2X2 PIC 9(12) VALUE 2 COMP-3.
05 RANDOM-NUMBER-BUILT PIC 9(12) COMP.
05 FIRST-PART PIC 9(12) COMP.
05 WORKING-NUMBER PIC 9(12) COMP.
05 LOW-RANGE PIC 9(12) VALUE ZERO.
05 HIGH-RANGE PIC 9(12) VALUE ZERO.
05 YOU-PROVIDE-SEED PIC X VALUE SPACE.
05 RUN-AGAIN PIC X VALUE SPACE.
05 PAUSE-FOR-A-SECOND PIC X VALUE SPACE.
01 SEED-TIME.
05 HOURS PIC 99.
05 MINUTES PIC 99.
05 SECONDS PIC 99.
05 MS PIC 99.
*
* LINKAGE SECTION.
* Not used during testing
01 RANDGEN-AREA.
05 COUNT-OF-ACCESSES PIC 9(12) VALUE ZERO.
05 RANDOM-NUMBERS-NEEDED PIC 9(12) VALUE ZERO.
05 RANDOM-NUMBER PIC 9(12) VALUE ZERO.
05 RANDOM-MSG PIC X(60) VALUE SPACE.
*
* PROCEDURE DIVISION USING RANDGEN-AREA.
* Not used during testing
*
PROCEDURE DIVISION.
100-RANDGEN-EDIT-Housekeeping.
MOVE SPACE TO RANDOM-MSG.
IF RANDOM-NUMBERS-NEEDED = ZERO
DISPLAY 'RANDOM-NUMBERS-NEEDED ' NO ADVANCING
ACCEPT RANDOM-NUMBERS-NEEDED.
IF RANDOM-NUMBERS-NEEDED NOT NUMERIC
MOVE 'RANDOM-NUMBERS-NEEDED NOT NUMERIC' TO RANDOM-MSG
GO TO 900-EXIT-RANDGEN.
IF RANDOM-NUMBERS-NEEDED = ZERO
MOVE 'RANDOM-NUMBERS-NEEDED = ZERO' TO RANDOM-MSG
GO TO 900-EXIT-RANDGEN.
IF COUNT-OF-ACCESSES NOT NUMERIC
MOVE 'COUNT-OF-ACCESSES NOT NUMERIC' TO RANDOM-MSG
GO TO 900-EXIT-RANDGEN.
IF COUNT-OF-ACCESSES GREATER THAN RANDOM-NUMBERS-NEEDED
MOVE 'COUNT-OF-ACCESSES > THAT RANDOM-NUMBERS-NEEDED'
TO RANDOM-MSG
GO TO 900-EXIT-RANDGEN.
IF YOU-PROVIDE-SEED = SPACE AND RANDOM-NUMBER = ZERO
DISPLAY 'DO YOU WANT TO PROVIDE SEED Y OR N: '
NO ADVANCING
ACCEPT YOU-PROVIDE-SEED.
IF RANDOM-NUMBER = ZERO AND
(YOU-PROVIDE-SEED = 'Y' OR 'y')
DISPLAY 'ENTER SEED ' NO ADVANCING
ACCEPT RANDOM-NUMBER.
IF RANDOM-NUMBER NOT NUMERIC
MOVE 'RANDOM-NUMBER NOT NUMERIC' TO RANDOM-MSG
GO TO 900-EXIT-RANDGEN.
200-RANDGEN-DATA-Housekeeping.
MOVE FUNCTION CURRENT-DATE (9:8) TO SEED-TIME.
IF COUNT-OF-ACCESSES = ZERO
COMPUTE LOW-RANGE =
((SECONDS * HOURS * MINUTES * MS) / 3).
COMPUTE RANDOM-NUMBER-BUILT = RANDOM-NUMBER + LOW-RANGE.
COMPUTE HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE.
MOVE X2-POWER TO 2X2.
300-SET-2X2-DIVISOR.
IF 2X2 < (HIGH-RANGE + 1)
COMPUTE 2X2 = 2X2 * X2-POWER
GO TO 300-SET-2X2-DIVISOR.
* *********************************************************
* IF FIRST TIME THROUGH AND YOU WANT TO BUILD A SEED. *
* *********************************************************
IF COUNT-OF-ACCESSES = ZERO AND RANDOM-NUMBER = ZERO
COMPUTE RANDOM-NUMBER-BUILT =
((SECONDS * HOURS * MINUTES * MS) + HIGH-RANGE).
IF COUNT-OF-ACCESSES = ZERO
DISPLAY 'SEED TIME ' SEED-TIME
' RANDOM-NUMBER-BUILT ' RANDOM-NUMBER-BUILT
' LOW-RANGE ' LOW-RANGE.
* *********************************************
* END OF BUILDING A SEED IF YOU WANTED TO *
* *********************************************
* ***************************************************
* THIS PROCESS IS WHERE THE RANDOM-NUMBER IS BUILT *
* ***************************************************
400-RANDGEN-FORMULA.
COMPUTE FIRST-PART = (5 * RANDOM-NUMBER-BUILT) + 7.
DIVIDE FIRST-PART BY 2X2 GIVING WORKING-NUMBER
REMAINDER RANDOM-NUMBER-BUILT.
IF RANDOM-NUMBER-BUILT > LOW-RANGE AND
RANDOM-NUMBER-BUILT < (HIGH-RANGE + 1)
GO TO 600-RANDGEN-CLEANUP.
GO TO 400-RANDGEN-FORMULA.
* *********************************************
* GOOD RANDOM NUMBER HAS BEEN BUILT *
* *********************************************
600-RANDGEN-CLEANUP.
ADD 1 TO COUNT-OF-ACCESSES.
COMPUTE RANDOM-NUMBER =
RANDOM-NUMBER-BUILT - LOW-RANGE.
* *******************************************************
* THE NEXT 3 LINE OF CODE ARE FOR TESTING ON CONSOLE *
* *******************************************************
DISPLAY RANDOM-NUMBER.
IF COUNT-OF-ACCESSES < RANDOM-NUMBERS-NEEDED
GO TO 100-RANDGEN-EDIT-Housekeeping.
900-EXIT-RANDGEN.
IF RANDOM-MSG NOT = SPACE
DISPLAY 'RANDOM-MSG: ' RANDOM-MSG.
MOVE ZERO TO COUNT-OF-ACCESSES RANDOM-NUMBERS-NEEDED RANDOM-NUMBER.
MOVE SPACE TO YOU-PROVIDE-SEED RUN-AGAIN.
DISPLAY 'RUN AGAIN Y OR N '
NO ADVANCING.
ACCEPT RUN-AGAIN.
IF (RUN-AGAIN = 'Y' OR 'y')
GO TO 100-RANDGEN-EDIT-Housekeeping.
ACCEPT PAUSE-FOR-A-SECOND.
GOBACK.
Une autre possibilité:
Vous pouvez utiliser un tableau de drapeaux. Et prenez la suivante quand elle est déjà choisie.
Mais attention, après 1000 appels, la fonction ne se terminera jamais, vous devez donc faire une sauvegarde.
Lorsque N est supérieur à 1000 et que vous devez prélever K échantillons aléatoires, vous pouvez utiliser un jeu contenant les échantillons jusqu'à présent. Pour chaque tirage, vous utilisez /Echantillon de rejet , qui correspond à une opération "presque" O(1), de sorte que la durée totale d'exécution est presque O(K) avec O(N) stockage.
Cet algorithme rencontre des collisions lorsque K est "proche de" N. Cela signifie que le temps d'exécution sera bien pire que O (K). Une solution simple consiste à inverser la logique afin que, pour K> N/2, vous gardiez une trace de tous les échantillons qui n'ont pas encore été dessinés. Chaque tirage enlève un échantillon du jeu de rejets.
L'autre problème évident avec l'échantillonnage de rejet est qu'il s'agit du stockage O(N), ce qui est une mauvaise nouvelle si N se situe dans les milliards ou plus. Cependant, il existe un algorithme qui résout ce problème. Cet algorithme s'appelle l'algorithme de Vitter d'après son inventeur. L'algorithme est décrit ici . L'algorithme de Gist of Vitter est qu'après chaque tirage, vous calculez un saut aléatoire en utilisant une certaine distribution qui garantit un échantillonnage uniforme.
La question Comment générer efficacement une liste de K nombres entiers non répétitifs compris entre 0 et une limite supérieure N est lié en tant que duplicata - et si vous voulez quelque chose qui est O(1) par aléatoire généré numéro (sans O(n) coût de démarrage)), il existe un simple Tweak de la réponse acceptée.
Créez une carte vide non ordonnée (une carte ordonnée vide passera de O (log k) par élément) d’entier à entier - au lieu d’utiliser un tableau initialisé . Définissez la valeur maximale sur 1000 si cette valeur est maximale,
La seule différence par rapport à l'utilisation d'un tableau initialisé est que l'initialisation des éléments est reportée/ignorée - mais elle générera les mêmes nombres exacts à partir du même PRNG.
La plupart des réponses fournies ici ne garantissent pas qu’elles ne renverront pas le même nombre deux fois. Voici une solution correcte:
int nrrand(void) {
static int s = 1;
static int start = -1;
do {
s = (s * 1103515245 + 12345) & 1023;
} while (s >= 1001);
if (start < 0) start = s;
else if (s == start) abort();
return s;
}
Je ne suis pas sûr que la contrainte soit bien spécifiée. On suppose qu’après 1000 autres sorties, une valeur est autorisée à être répétée, mais que naïvement permet à 0 de suivre immédiatement après 0 tant qu’ils apparaissent tous les deux à la fin et au début de 1000 autres valeurs entre les répétitions, cela force une situation dans laquelle la séquence se répète exactement de la même manière à chaque fois, car aucune autre valeur ne s'est produite en dehors de cette limite.
Voici une méthode qui garantit toujours au moins 500 autres valeurs avant qu’une valeur ne puisse être répétée:
int nrrand(void) {
static int h[1001];
static int n = -1;
if (n < 0) {
int s = 1;
for (int i = 0; i < 1001; i++) {
do {
s = (s * 1103515245 + 12345) & 1023;
} while (s >= 1001);
/* If we used `i` rather than `s` then our early results would be poorly distributed. */
h[i] = s;
}
n = 0;
}
int i = Rand(500);
if (i != 0) {
i = (n + i) % 1001;
int t = h[i];
h[i] = h[n];
h[n] = t;
}
i = h[n];
n = (n + 1) % 1001;
return i;
}
for i from n−1 downto 1 do
j ← random integer such that 0 ≤ j ≤ i
exchange a[j] and a[i]
C’est en fait O(n-1) car vous n’avez besoin que d’un échange pour les deux derniers
Ceci est C #
public static List<int> FisherYates(int n)
{
List<int> list = new List<int>(Enumerable.Range(0, n));
Random Rand = new Random();
int swap;
int temp;
for (int i = n - 1; i > 0; i--)
{
swap = Rand.Next(i + 1); //.net Rand is not inclusive
if(swap != i) // it can stay in place - if you force a move it is not a uniform shuffle
{
temp = list[i];
list[i] = list[swap];
list[swap] = temp;
}
}
return list;
}
S'il vous plaît voir ma réponse à https://stackoverflow.com/a/46807110/8794687
C'est l'un des algorithmes les plus simples ayant une complexité temporelle moyenne O (s log s), s indiquant la taille de l'échantillon. Il existe également quelques liens vers des algorithmes de table de hachage dont la complexité est supposée être O (s).