J'étais curieux de savoir comment cela a std:next_permutation
été implémenté, j'ai donc extrait la gnu libstdc++ 4.7
version et nettoyé les identifiants et le formatage pour produire la démo suivante ...
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
template<typename It>
bool next_permutation(It begin, It end)
{
if (begin == end)
return false;
It i = begin;
++i;
if (i == end)
return false;
i = end;
--i;
while (true)
{
It j = i;
--i;
if (*i < *j)
{
It k = end;
while (!(*i < *--k))
/* pass */;
iter_swap(i, k);
reverse(j, end);
return true;
}
if (i == begin)
{
reverse(begin, end);
return false;
}
}
}
int main()
{
vector<int> v = { 1, 2, 3, 4 };
do
{
for (int i = 0; i < 4; i++)
{
cout << v[i] << " ";
}
cout << endl;
}
while (::next_permutation(v.begin(), v.end()));
}
Le résultat est comme prévu: http://ideone.com/4nZdx
Mes questions sont: comment ça marche? Quel est le sens de i
, j
et k
? Quelle valeur ont-ils aux différentes étapes de l'exécution? Qu'est-ce qu'une esquisse d'une preuve de son exactitude?
Clairement, avant d'entrer dans la boucle principale, il vérifie simplement les cas triviaux de liste d'éléments 0 ou 1. A l'entrée de la boucle principale, je pointe vers le dernier élément (pas une dernière extrémité) et la liste est longue d'au moins 2 éléments.
Que se passe-t-il dans le corps de la boucle principale?
la source
Réponses:
Regardons quelques permutations:
Comment passer d'une permutation à l'autre? Tout d'abord, regardons les choses un peu différemment. Nous pouvons voir les éléments sous forme de chiffres et les permutations sous forme de nombres . En regardant le problème de cette manière, nous voulons ordonner les permutations / nombres dans l'ordre "croissant" .
Lorsque nous commandons des numéros, nous voulons "les augmenter du plus petit montant". Par exemple en comptant on ne compte pas 1, 2, 3, 10, ... car il y a encore 4, 5, ... entre et bien que 10 soit plus grand que 3, il manque des nombres qui peuvent être obtenus par augmentant 3 par un montant plus petit. Dans l'exemple ci-dessus, nous voyons que cela
1
reste le premier nombre pendant une longue période car il y a beaucoup de réorganisations des 3 derniers "chiffres" qui "augmentent" la permutation d'une quantité plus petite.Alors, quand enfin «utiliser» le
1
? Lorsqu'il n'y a plus de permutations des 3 derniers chiffres.Et quand n'y a-t-il plus de permutations des 3 derniers chiffres? Lorsque les 3 derniers chiffres sont dans l'ordre décroissant.
Ah! Ceci est essentiel pour comprendre l'algorithme. Nous ne modifions la position d'un "chiffre" que lorsque tout à droite est dans l'ordre décroissant car si ce n'est pas dans l'ordre décroissant, il y a encore plus de permutations à faire (c'est- à- dire que nous pouvons "augmenter" la permutation d'un plus petit montant) .
Revenons maintenant au code:
À partir des 2 premières lignes de la boucle,
j
est un élément eti
est l'élément avant lui.Ensuite, si les éléments sont dans l'ordre croissant, (
if (*i < *j)
) faites quelque chose.Sinon, si le tout est dans l'ordre décroissant, (
if (i == begin)
) alors c'est la dernière permutation.Sinon, on continue et on voit que j et i sont essentiellement décrémentés.
Nous comprenons maintenant la
if (i == begin)
partie, donc tout ce que nous devons comprendre est laif (*i < *j)
partie.Notez également: "Alors si les éléments sont dans l'ordre croissant ..." ce qui confirme notre observation précédente que nous n'avons besoin de faire quelque chose qu'à un chiffre "quand tout à droite est dans l'ordre décroissant". L'
if
instruction d' ordre croissant trouve essentiellement l'endroit le plus à gauche où «tout ce qui est à droite est dans l'ordre décroissant».Regardons à nouveau quelques exemples:
Nous voyons que lorsque tout ce qui se trouve à droite d'un chiffre est dans l'ordre décroissant, nous trouvons le chiffre suivant le plus grand et le mettons devant , puis les chiffres restants dans l'ordre croissant .
Regardons le code:
Eh bien, puisque les choses à droite sont dans l'ordre décroissant, pour trouver le "prochain plus grand chiffre", nous devons juste itérer à partir de la fin, ce que nous voyons dans les 3 premières lignes de code.
Ensuite, nous échangeons le "prochain plus grand chiffre" vers l'avant avec l'
iter_swap()
instruction et puis, puisque nous savons que ce chiffre était le prochain plus grand, nous savons que les chiffres à droite sont toujours dans l'ordre décroissant, donc pour le mettre dans l'ordre croissant, nous devons juste le fairereverse()
.la source
Combinatorics
, mais c'est le plus classique.L'implémentation gcc génère des permutations dans l'ordre lexicographique. Wikipédia l' explique comme suit:
la source
Knuth approfondit cet algorithme et ses généralisations dans les sections 7.2.1.2 et 7.2.1.3 de The Art of Computer Programming . Il l'appelle "Algorithme L" - apparemment, il remonte au 13ème siècle.
la source
Voici une implémentation complète utilisant d'autres algorithmes de bibliothèque standard:
Démo
la source
is_final_permutation
est plus informatif quebegin == end - 1
. L'appelis_sorted_until
/upper_bound
sépare la logique de permutation de ces opérations et rend cela beaucoup plus compréhensible. De plus, upper_bound est une recherche binaire, alors qu'ellewhile (!(*i < *--k));
est linéaire, donc c'est plus performant.Il existe une implémentation possible explicite sur cppreference en utilisant
<algorithm>
.Changez le contenu en permutation lexicographique suivante (sur place) et retournez true s'il existe, sinon triez et retournez false s'il n'existe pas.
la source