3 novembre 2021
Ce texte fait partie d’une petite collection de notes mise à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 2.0 France.
Dernières corrections : 6 novembre.
Le problème est une question classique d’algorithmique combinatoire :
Donnez un algorithme efficace qui, à partir d’une permutation, détermine la permutation suivante dans l’ordre lexicographique.
Par exemple, l’algorithme trouvera que la permutation est suivie par .
Notation. La permutation est la bijection sur telle que et .
On trouve facilement cet algorithme sur internet, je tente ici de donner quelques éclaircissements.
Cet algorithme permet de construire un programme de génération de permutations dans l’ordre lexicographique, en remarquant que pour tout
La construction de repose sur quelques observations simples.
Quand on connait le début d’une permutation, il est facile de déterminer l’ensemble des permutations qui la complètent.
Par exemple, pour compléter , permutation de longueur 6
Nous sommes intéressés par la séquence de permutations dans l’ordre lexicographique, obtenue en complétant le “préfixe” successivement par les permutations du “reste” dans l’ordre.
Cette séquence commence par et finit par , qui ont le “reste” en ordre croissant et décroissant respectivement.
De façon évidente, chaque permutation se décompose de façon unique en concaténation d’un préfixe et d’un suffixe qui soit la plus longue suite décroissante possible.
Exemple, pour
On remarque que
Une propriété intéressante de cette décomposition est que toute permutation est la plus grande, dans l’ordre lexicographique, de celles qui ont le même préfixe.
La permutation suivante, si elle existe (ce qui se voit au préfixe non vide), commence donc par la séquence qui s’obtient en augmentant le dernier élément du préfixe, et en complétant. Exemple,
Attention, il ne suffit pas de remplacer le dernier élément du préfixe par le suivant. Par exemple le préfixe de se termine par 2, qu’on ne peut pas remplacer par qui apparaît déjà dans le préfixe. Il faut prendre 5, qui est le plus petit élément du suffixe qui soit supérieur à 2.
Propriété Cet élément existe toujours.
En effet, le suffixe n’est pas vide, et par construction son premier élément est plus grand que le dernier du préfixe.
Une propriété des séquences décroissantes simplifiera la construction de la première permutation suivante :
Propriété soit une séquence décroissante et un nombre. Si il existe un tel que soit le plus petit des éléments qui soient supérieurs à , alors la séquence obtenue en remplaçant par est décroissante.
En effet,
Il en résulte que la séquence obtenue est décroissante.
Les étapes de la construction de la permutation suivante
La remarque précédente a permis de traduire la dernière étape “compléter le suffixe par le reste des éléments en ordre croissant” par un simple retournement de tableau (“miroir”).
Le programme ci-dessous, en Fortran 95, affiche les permutations de longueur 4.
L’enumération des permutations met en oeuvre deux fonctions qui ont un tableau et sa taille en paramètre.
get_first_permutation
remplit le tableau avec la permutation initiale (1,2,…)get_next_permutation
transforme la permutation contenue dans le tableau en la permutation suivanteLe booléen retourné par les deux fonctions indiquent le succès de l’opération.
!: @file test_permutations.f95
!! test program for the permutations module
!! displays all 24 permutations of (1, 2, 3, 4)
program test_permutations
use permutations
implicit none
integer :: array(1:4)
logical :: exists
integer :: number
= 0
number = get_first_permutation(array, size(array))
exists do while (exists)
= number + 1
number call print_permutation
= get_next_permutation(array, size(array))
exists end do
contains
subroutine print_permutation
integer :: i
write(*, "(I3,': ',*(I3))") number, (array(i), i = 1, size(array))
end subroutine print_permutation
end program test_permutations
permutations
Les fonctions sont fournies par le module permutations
!> @file permutations.f95
!! @brief generation of permutation in lexicographic order
module permutations
implicit none
private
public :: get_first_permutation, get_next_permutation
contains
!> get the first permutation (1, 2...size) into an array
!! @param array array to be filled
!! @param size length of the permutation
!! @return true if possible
function get_first_permutation(array, size) result(found)
integer, intent(IN) :: size
integer, intent(OUT) :: array(1:size)
logical :: found
integer :: i
= (size >= 1)
found if (.not. found) return
do i = 1, size
= i
array(i) end do
end function get_first_permutation
!> change a given permutation into the next one if possible
!! @param array array containing the permutations
!! @param size length of the permutation
!! @return true if possible
function get_next_permutation(array, size) result(found)
integer, intent(IN) :: size
integer, intent(INOUT) :: array(1:size)
logical :: found
integer :: prefix_length, index
= find_prefix_length(array, size)
prefix_length = (prefix_length > 0)
found if (.not. found) return ! array contains the last permutation
index = find_next_in_prefix(array, size, array(prefix_length))
call swap(array(prefix_length), array(index))
call reverse_suffix(array, size, prefix_length + 1)
end function get_next_permutation
!
! private part
!
function find_prefix_length(array, size) result(length)
integer, intent(IN) :: size
integer, intent(IN) :: array(1:size)
integer :: length
= size - 1
length do while ((length > 0) .and. (array(length) >= array(length + 1)))
= length - 1
length end do
end function find_prefix_length
function find_next_in_prefix(array, size, value) result(index)
integer, intent(IN) :: size, value
integer, intent(IN) :: array(1:size)
integer :: index
index = size
do while (array(index) <= value)
index = index - 1
end do
end function find_next_in_prefix
subroutine reverse_suffix(array, size, suffix_start)
integer, intent(IN) :: size, suffix_start
integer, intent(INOUT) :: array(1:size)
integer :: left, right
= suffix_start
left = size
right do while (left < right)
call swap (array(left), array(right))
= left + 1
left = right - 1
right end do
end subroutine reverse_suffix
subroutine swap(a, b)
integer, intent(inout) :: a, b
integer :: c
= a
c = b
a = c
b end subroutine swap
end module permutations
gfortran -std=f95 -Wall -Wextra -pedantic \
$ test_permutations.f95 permutations.f95 \
-o test_permutations
./test_permutations
$ 1: 1 2 3 4
2: 1 2 4 3
.... 20 lignes supprimées....
23: 4 3 1 2
24: 4 3 2 1
Sur le même principe, le programme C suivant énumère les permutations sur . En effet, en C les tableaux commencent par l’indice 0, et il est a priori possible que les éléments de la permutation servent d’indices dans d’autres tableaux.
#include <stdio.h>
#include <stdbool.h>
bool get_first_permutation(int array[], int size);
bool get_next_permutation(int array[], int size);
void print_array(int array[], int size);
int main()
{int array[4];
int number = 0;
for (bool exists = get_first_permutation(array, 4);
exists;4)) {
exists = get_next_permutation(array, 1;
number += "%4d : ", number);
printf(4);
print_array(array, "\n");
printf(
}
}
void print_array(int array[], int size)
{for (int i = 0; i < size; i++) {
"%3d", array[i]);
printf(
}
}
bool get_first_permutation(int array[], int size)
{if (size <= 0) {
return false;
}for (int i = 0; i < size; i++) {
array[i] = i;
}return true;
}
void swap(int *pa, int *pb)
{int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
bool get_next_permutation(int array[], int size)
{int suffix_start = size-1;
while (suffix_start > 0
1] >= array[suffix_start]) {
&& array[suffix_start -
suffix_start --;
}if (suffix_start == 0) {
return false;
}int index = size -1;
while (array[suffix_start - 1] > array[index]) {
index --;
}1], & array[index]);
swap (& array[suffix_start - for (int left = suffix_start, right = size - 1;
left < right;
left++, right-- ) {
swap (& array[left], &array[right]);
}
return true;
}