Je cherche un algorithme pour générer des permutations d'un ensemble de telle manière que je puisse en faire une liste paresseuse dans Clojure. c'est-à-dire que je voudrais parcourir une liste de permutations où chaque permutation n'est pas calculée jusqu'à ce que je la demande, et toutes les permutations ne doivent pas être stockées en mémoire à la fois.
Alternativement, je recherche un algorithme où, étant donné un certain ensemble, il retournera la "prochaine" permutation de cet ensemble, de telle manière que l'appel répété de la fonction sur sa propre sortie parcourra toutes les permutations de l'ensemble d'origine, dans un ordre (quel que soit l'ordre n'a pas d'importance).
Existe-t-il un tel algorithme? La plupart des algorithmes de génération de permutation que j'ai vus ont tendance à les générer tous en même temps (généralement de manière récursive), ce qui ne se transforme pas en ensembles très grands. Une implémentation dans Clojure (ou un autre langage fonctionnel) serait utile mais je peux le comprendre à partir du pseudocode.
Oui, il y a un algorithme de "prochaine permutation", et c'est assez simple aussi. La bibliothèque de modèles standard C++ (STL) a même une fonction appelée next_permutation
.
L'algorithme trouve en fait la permutation suivante - la suivante lexicographiquement. L'idée est la suivante: supposons que l'on vous donne une séquence, disons "32541". Quelle est la prochaine permutation?
Si vous y réfléchissez, vous verrez que c'est "34125". Et vos pensées étaient probablement quelque chose comme ceci: dans "32541",
L'algorithme consiste à implémenter précisément ce raisonnement:
Vous pouvez faire (1.) efficacement en commençant à la fin et en reculant tant que l'élément précédent n'est pas plus petit que l'élément actuel. Vous pouvez faire (2.) en échangeant simplement le "4" avec le "2", vous aurez donc "34521". Une fois que vous faites cela, vous pouvez éviter d'utiliser un algorithme de tri pour (3.), parce que la queue a été, et est toujours (pensez-y), trié par ordre décroissant, il n'a donc qu'à être inversé.
Le code C++ fait précisément cela (regardez la source dans /usr/include/c++/4.0.0/bits/stl_algo.h
sur votre système, ou consultez cet article ); il devrait être simple de le traduire dans votre langue: [Lire "BidirectionalIterator" comme "pointeur", si vous n'êtes pas familier avec les itérateurs C++. Le code renvoie false
s'il n'y a pas de permutation suivante, c'est-à-dire que nous sommes déjà dans l'ordre décroissant.]
template <class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first,
BidirectionalIterator last) {
if (first == last) return false;
BidirectionalIterator i = first;
++i;
if (i == last) return false;
i = last;
--i;
for(;;) {
BidirectionalIterator ii = i--;
if (*i <*ii) {
BidirectionalIterator j = last;
while (!(*i <*--j));
iter_swap(i, j);
reverse(ii, last);
return true;
}
if (i == first) {
reverse(first, last);
return false;
}
}
}
Il peut sembler que cela peut prendre O(n) temps par permutation, mais si vous y réfléchissez plus attentivement, vous pouvez prouver qu'il faut O (n!) Temps pour toutes les permutations au total , donc seulement O(1) - temps constant - par permutation.
La bonne chose est que l'algorithme fonctionne même lorsque vous avez une séquence avec des éléments répétés: avec, disons, "232254421", il trouverait la queue comme "54421", permutez le "2" et "4" (donc "232454221" ), inversez le reste en donnant "232412245", qui est la prochaine permutation.
En supposant que nous parlons de l'ordre lexicographique sur les valeurs permutées, il existe deux approches générales que vous pouvez utiliser:
n
th, tout en comptant n
à partir de 0.Pour ceux (comme moi ;-) qui ne parlent pas C++ comme des natifs, l'approche 1 peut être implémentée à partir du pseudo-code suivant, en supposant l'indexation à base zéro d'un tableau avec l'index zéro sur la "gauche" (en substituant une autre structure , comme une liste, est "laissé comme exercice" ;-):
1. scan the array from right-to-left (indices descending from N-1 to 0)
1.1. if the current element is less than its right-hand neighbor,
call the current element the pivot,
and stop scanning
1.2. if the left end is reached without finding a pivot,
reverse the array and return
(the permutation was the lexicographically last, so its time to start over)
2. scan the array from right-to-left again,
to find the rightmost element larger than the pivot
(call that one the successor)
3. swap the pivot and the successor
4. reverse the portion of the array to the right of where the pivot was found
5. return
Voici un exemple commençant par une permutation actuelle de CADB:
1. scanning from the right finds A as the pivot in position 1
2. scanning again finds B as the successor in position 3
3. swapping pivot and successor gives CBDA
4. reversing everything following position 1 (i.e. positions 2..3) gives CBAD
5. CBAD is the next permutation after CADB
Pour la seconde approche (calcul direct de la n
ème permutation), rappelez-vous qu'il existe N!
permutations d'éléments N
. Par conséquent, si vous permutez les éléments N
, le premier (N-1)!
les permutations doivent commencer par le plus petit élément, le suivant (N-1)!
les permutations doivent commencer par la deuxième plus petite, et ainsi de suite. Cela conduit à l'approche récursive suivante (toujours en pseudo-code, numérotant les permutations et les positions à partir de 0):
To find permutation x of array A, where A has N elements:
0. if A has one element, return it
1. set p to ( x / (N-1)! ) mod N
2. the desired permutation will be A[p] followed by
permutation ( x mod (N-1)! )
of the elements remaining in A after position p is removed
Ainsi, par exemple, la 13e permutation de ABCD se trouve comme suit:
perm 13 of ABCD: {p = (13 / 3!) mod 4 = (13 / 6) mod 4 = 2; ABCD[2] = C}
C followed by perm 1 of ABD {because 13 mod 3! = 13 mod 6 = 1}
perm 1 of ABD: {p = (1 / 2!) mod 3 = (1 / 2) mod 2 = 0; ABD[0] = A}
A followed by perm 1 of BD {because 1 mod 2! = 1 mod 2 = 1}
perm 1 of BD: {p = (1 / 1!) mod 2 = (1 / 1) mod 2 = 1; BD[1] = D}
D followed by perm 0 of B {because 1 mod 1! = 1 mod 1 = 0}
B (because there's only one element)
DB
ADB
CADB
Par ailleurs, la "suppression" d'éléments peut être représentée par un tableau parallèle de booléens qui indique quels éléments sont encore disponibles, il n'est donc pas nécessaire de créer un nouveau tableau à chaque appel récursif.
Donc, pour parcourir les permutations de ABCD, il suffit de compter de 0 à 23 (4! -1) et de calculer directement la permutation correspondante.
Plus d'exemples d'algorithmes de permutation pour les générer.
Source: http://www.ddj.com/architect/201200326
1.
PROGRAM TestFikePerm;
CONST marksize = 5;
VAR
marks : ARRAY [1..marksize] OF INTEGER;
ii : INTEGER;
permcount : INTEGER;
PROCEDURE WriteArray;
VAR i : INTEGER;
BEGIN
FOR i := 1 TO marksize
DO Write ;
WriteLn;
permcount := permcount + 1;
END;
PROCEDURE FikePerm ;
{Outputs permutations in nonlexicographic order. This is Fike.s algorithm}
{ with tuning by J.S. Rohl. The array marks[1..marksizn] is global. The }
{ procedure WriteArray is global and displays the results. This must be}
{ evoked with FikePerm(2) in the calling procedure.}
VAR
dn, dk, temp : INTEGER;
BEGIN
IF
THEN BEGIN { swap the pair }
WriteArray;
temp :=marks[marksize];
FOR dn := DOWNTO 1
DO BEGIN
marks[marksize] := marks[dn];
marks [dn] := temp;
WriteArray;
marks[dn] := marks[marksize]
END;
marks[marksize] := temp;
END {of bottom level sequence }
ELSE BEGIN
FikePerm;
temp := marks[k];
FOR dk := DOWNTO 1
DO BEGIN
marks[k] := marks[dk];
marks[dk][ := temp;
FikePerm;
marks[dk] := marks[k];
END; { of loop on dk }
marks[k] := temp;l
END { of sequence for other levels }
END; { of FikePerm procedure }
BEGIN { Main }
FOR ii := 1 TO marksize
DO marks[ii] := ii;
permcount := 0;
WriteLn ;
WrieLn;
FikePerm ; { It always starts with 2 }
WriteLn ;
ReadLn;
END.
2.
PROGRAM TestLexPerms;
CONST marksize = 5;
VAR
marks : ARRAY [1..marksize] OF INTEGER;
ii : INTEGER;
permcount : INTEGER;
PROCEDURE WriteArray;
VAR i : INTEGER;
BEGIN
FOR i := 1 TO marksize
DO Write ;
permcount := permcount + 1;
WriteLn;
END;
PROCEDURE LexPerm ;
{ Outputs permutations in lexicographic order. The array marks is global }
{ and has n or fewer marks. The procedure WriteArray () is global and }
{ displays the results. }
VAR
work : INTEGER:
mp, hlen, i : INTEGER;
BEGIN
IF
THEN BEGIN { Swap the pair }
work := marks[1];
marks[1] := marks[2];
marks[2] := work;
WriteArray ;
END
ELSE BEGIN
FOR mp := DOWNTO 1
DO BEGIN
LexPerm<>;
hlen := DIV 2;
FOR i := 1 TO hlen
DO BEGIN { Another swap }
work := marks[i];
marks[i] := marks[n - i];
marks[n - i] := work
END;
work := marks[n]; { More swapping }
marks[n[ := marks[mp];
marks[mp] := work;
WriteArray;
END;
LexPerm<>
END;
END;
BEGIN { Main }
FOR ii := 1 TO marksize
DO marks[ii] := ii;
permcount := 1; { The starting position is permutation }
WriteLn < Starting position: >;
WriteLn
LexPerm ;
WriteLn < PermCount is , permcount>;
ReadLn;
END.
3.
PROGRAM TestAllPerms;
CONST marksize = 5;
VAR
marks : ARRAY [1..marksize] of INTEGER;
ii : INTEGER;
permcount : INTEGER;
PROCEDURE WriteArray;
VAR i : INTEGER;
BEGIN
FOR i := 1 TO marksize
DO Write ;
WriteLn;
permcount := permcount + 1;
END;
PROCEDURE AllPerm (n : INTEGER);
{ Outputs permutations in nonlexicographic order. The array marks is }
{ global and has n or few marks. The procedure WriteArray is global and }
{ displays the results. }
VAR
work : INTEGER;
mp, swaptemp : INTEGER;
BEGIN
IF
THEN BEGIN { Swap the pair }
work := marks[1];
marks[1] := marks[2];
marks[2] := work;
WriteArray;
END
ELSE BEGIN
FOR mp := DOWNTO 1
DO BEGIN
ALLPerm<< n - 1>>;
IF >
THEN swaptemp := 1
ELSE swaptemp := mp;
work := marks[n];
marks[n] := marks[swaptemp};
marks[swaptemp} := work;
WriteArray;
AllPerm< n-1 >;
END;
END;
BEGIN { Main }
FOR ii := 1 TO marksize
DO marks[ii] := ii
permcount :=1;
WriteLn < Starting position; >;
WriteLn;
Allperm < marksize>;
WriteLn < Perm count is , permcount>;
ReadLn;
END.
Vous devriez vérifier article Permutations sur wikipeda. Il existe également le concept de factororadique nombres.
Quoi qu'il en soit, le problème mathématique est assez difficile.
Dans C#
vous pouvez utiliser un iterator
et arrêter l'algorithme de permutation à l'aide de yield
. Le problème avec ceci est que vous ne pouvez pas aller et venir, ou utiliser un index
.
la fonction de permutations dans clojure.contrib.lazy_seqs prétend déjà faire exactement cela.