• Aucun résultat trouvé

Une permutation sur l’ensemble {0, . . . , n−1} est une fonction bijective p : {0, . . . , n−1} → {0, . . . , n − 1}. On repr´esente une telle permutation par un tableau d’entiers de taille n qui contient les entiers {0, . . . , n − 1} exactement une fois. Soit id la permutation identit´e et ◦ la composition de permutations. L’ensemble des permutations sur l’ensemble {0, . . . , n − 1} est un groupe commutatif. En particulier, chaque permutation admet une permutation inverse par rapport `a la composition. Un point fixe d’une permutation p est un ´el´ement i ∈ {0, . . . , n − 1} tel que p(i) = i.

Exercice 4 Programmez une fonction C d’en tˆete void comp(int n, int r[n], int p[n], int q[n]) qui prend en entr´ee deux permutations (repr´esent´ees par les tableaux p et q) et ´ecrit dans le premier tableau r la repr´esentation de la permutation composition ‘p ◦ q0.

Exercice 5 Programmez une fonction C d’en tˆete void inv(int n, int p[n], int q[n]) qui prend en entr´ee une permutation (repr´esent´ee par le tableau p) et ´ecrit dans le tableau q la repr´esentation de la permutation inverse de p.

Exercice 6 Programmez une fonction C d’en tˆete int nbpointfixe(int n, int p[n]) qui prend en entr´ee une permutation (repr´esent´ee par le tableau p) et retourne le nombre de points fixes de p.

´

Enum´erer les permutations

On consid`ere maintenant le probl`eme de concevoir un programme qui ´enum`ere toutes les permutations sur {0, . . . , n − 1}. Par exemple, pour n = 3 le programme pourrait imprimer :

0 1 2 0 2 1 1 0 2 1 2 0 2 0 1 2 1 0

Pour pr´eparer le terrain on peut d’abord consid´erer le probl`eme suivant : ´enum´erer toutes les fonctions sur {0, . . . , n − 1}. Une fonction sur {0, . . . , n − 1} peut ˆetre repr´esent´ee par un tableau avec n ´el´ements qui varient sur {0, . . . , n − 1}. ´Enum´erer les fonctions revient alors `

a ´enum´erer de tels tableaux. On peut suivre l’algorithme suivant : au pas i on ´ecrit dans la cellule i du tableau la valeur j pour j = 0, . . . , n − 1. Pour chaque j, on v´erifie si i = n − 1. Si c’est le cas on imprime le tableau et sinon on incr´emente i et on recommence. Voici un codage (`a compl´eter) de cet algorithme en C.

1 v o i d f o n c t (int g [] , int i , int n ){

2 int j ; 3 for ( j =0; j < n ; j + + ) { 4 g [ i ]= j ; 5 if ( i ==( n - 1 ) ) { 6 /* i m p r i m e r f o n c t i o n */ } 7 e l s e { 8 f o n c t ( g , i +1 , n ) ; } } } 9 int m a i n(){ 10 int n ; 11 /* l i r e n */ 12 int g [ n ]; 13 f o n c t ( g ,0 , n ); 14 r e t u r n 0;}

Exercice 7 Programmez une fonction C qui v´erifie si une fonction sur {0, . . . , n − 1} est une permutation. Modifiez le programme ci-dessus pour qu’il imprime seulement les permutations.

Le programme pour ´enum´erer les permutations d´eriv´e de l’exercice 7 n’est pas parti-culi`erement efficace car il ´enum`ere toutes les fonctions sur {0, . . . , n−1} pour ensuite imprimer seulement les permutations. En g´en´eral, on aura nn fonctions et seulement n! permutations. Par exemple, pour n = 7, on a 77 = 823543 >> 5040 = 7!.

Tri et permutations 61

Une approche plus efficace consiste donc `a d´etecter aussi tˆot que possible les fonctions partiellement sp´ecifi´ees qui n’ont aucune chance de devenir des permutations. Une condition n´ecessaire et suffisante pour qu’une fonction partiellement sp´ecifi´ee sur {0, . . . , n − 1} puisse devenir une permutation est qu’il n’y ait pas un ´el´ement r´ep´et´e dans l’image de la fonction (pour construire une permutation il faut utiliser les ´el´ements dans {0, . . . , n − 1} exactement une fois). On va donc introduire un deuxi`eme tableau dans lequel on va se souvenir des ´el´ements d´ej`a utilis´es dans la construction d’une permutation. Une programmation possible est la suivante.

1 v o i d p e r m (int p [] , s h o r t f [] , int i , int n ){

2 int j ; 3 for ( j =0; j < n ; j + + ) { 4 if ( f [ j ]){ 5 f [ j ] = 0 ; /* j n ’ est p l u s d i s p o n i b l e */ 6 p [ i ]= j ; 7 if ( i ==( n - 1 ) ) { 8 /* i m p r i m e r p e r m u t a t i o n */} 9 e l s e { 10 p e r m ( p , f , i +1 , n );} 11 f [ j ] = 1 ; /* j `a n o u v e a u d i s p o n i b l e */ }}} 12 int m a i n(){ 13 int n ; 14 /* l i r e n */ 15 int g [ n ]; 16 s h o r t f [ n ]; 17 int j ; 18 for ( j =0; j < n ; j + + ) { 19 f [ j ] = 1 ; } /* t o u s j d i s p o n i b l e s */ 20 p e r m ( p , f ,0 , n ); 21 r e t u r n 0;}

Encore une autre possibilit´e est de supposer qu’au d´ebut le tableau contient une per-mutation. Ensuite on fait varier un indice i de 0 `a n − 1, un indice j entre i et n − 1. Pour chaque couple (i, j), on ´echange l’´el´ement d’indice i avec celui d’indice j, on appelle la fonction d’´enum´eration et on ´echange encore.

1 v o i d p e r m (int t [] ,int n , int i ){

2 if ( i ==( n - 1 ) ) { 3 /* i m p r i m e r t */} 4 e l s e{ 5 int j ; 6 for( j = i ; j < n ; j + + ) { 7 /* ´e c h a n g e r t [ i ] et t [ j ] */ 8 p e r m ( t , n , i + 1 ) ; 9 /* ´e c h a n g e r t [ i ] et t [ j ] * / } } }

Remarque 8 Comme pour le probl`eme du d´eplacement de la tour d’Hanoi (section 5.2) et du tri par fusion (section 7.1), la simplicit´e de ces programmes repose sur l’utilisation d’une fonction r´ecursive. Le lecteur est invit´e `a modifier les programmes afin de tracer tous les appels `a la fonction perm.

Chapitre 8

Types structure et union

Le langage C comporte un certain nombre de types primitifs. Aussi les tableaux et les pointeurs nous permettent de cr´eer des nouveaux types. Par exemple, avec int a[] ; on d´eclare a comme un tableau d’entiers. Dans ce chapitre, on va introduire des nouvelles fa¸cons de construire des types.

8.1 Structures

Souvent on a besoin d’agr´eger des donn´ees de types diff´erents. Par exemple, le nom (string) et l’ˆage (int) d’un patient. On pourrait d´efinir un nouveau type produit :

fiche = string × int .

Un ´el´ement de type fiche serait donc un couple. En utilisant les projections on pourrait acc´eder au premier et deuxi`eme composant. En programmation, on pr´ef`ere donner des noms mn´emoniques aux projections. On parle alors de structures (en C) ou d’enregistrements (re-cords en anglais) dans d’autres langages.

La d´eclaration du type fiche en C pourrait prendre la forme suivante o`u l’on suppose que 10 caract`eres suffisent pour repr´esenter un nom :

1 s t r u c t f i c h e {c h a r nom [ 1 0 ] ; int age ;};

Si x est une valeur de type struct fiche on peut acc´eder au premier composant avec x.nom et au deuxi`eme avec x.age. On insiste sur le fait que le nom du type est struct fiche et non pas fiche. Cependant, il est possible d’utiliser le nom fiche en posant :

1 t y p e d e f s t r u c t f i c h e f i c h e ;

La valeur d’une variable de type fiche est son contenu et non pas l’adresse de m´emoire o`u ce contenu est m´emoris´e. De ce point de vue, les variables de type structure se comportent comme les variables de type primitif (int, float,. . .) et non pas comme les variables de type tableau. Si l’on souhaite utiliser une fonction pour modifier une structure on doit soit passer l’adresse de la structure (comme dans (1)) soit recevoir une copie modifi´ee de la structure (comme dans (2)). Si on proc`ede comme dans (3), la structure de la fonction appelante n’est pas modifi´ee. Ainsi, dans (4) le nom imprim´e sera georges.

1 f i c h e f ( f i c h e p ){

2 s t r c p y( p . nom ," f r a n k ");

3 r e t u r n p ;} 4 v o i d g ( f i c h e * p ){ 5 s t r c p y((* p ). nom ," g e o r g e s " ) ; } 6 v o i d m a i n(){ 7 f i c h e p ; 8 s t r c p y( p . nom ," m a r i u s "); 9 p . age = 2 7 ; 10 p = f ( p ); \ \ ( 1 ) 11 g (& p ); \ \ ( 2 ) 12 f ( p ); \ \ ( 3 )

13 p r i n t f(" nom =% s , age =% d \ n " ,( p . nom ) ,( p . age ) ) ; } \ \ ( 4 )