• Aucun résultat trouvé

Fonctionnelles et polymorphisme

5.3 Tri par insertion

Nous impl´ementons le tri par insertion, qui est un algorithme naturellement r´ecursif. On suppose qu’une sous-partie du tableau `a trier est d´ej`a tri´ee et on y ins`ere `a la bonne place le prochain ´el´ement de la partie non tri´ee du tableau. Nous en donnons une version fonctionnelle sur les listes.

Tri par insertion 79

Tri sur listes

L’id´ee est qu’il est facile de ranger un ´el´ement `a sa place dans une liste d’´el´ements d´ej`a tri´ee. Nous supposons donc avoir d´ej`a ´ecrit une fonction ins`ere qui ins`ere un ´el´ement `a la bonne place dans une liste tri´ee. Nous ´ecrivons maintenant la fonction de tri. Cette fonction travaille sur des listes ; elle doit donc envisager les deux cas possibles de listes :

let tri_par_insertion = function | [] -> ...

| x :: reste -> ... ;;

Le cas de la liste vide est simple : une liste vide est ´evidemment tri´ee ; on renvoie donc la liste vide.

let tri_par_insertion = function | [] -> []

| x :: reste -> ... ;;

Dans l’autre cas, on va commencer par trier le reste de la liste. C’est d´ej`a possible, bien que nous n’ayons pas encore ´ecrit notre fonction de tri : il suffit d’appeler r´ecursivement la fonction tri_par_insertion que nous sommes justement en train d’´ecrire . . .

let rec tri_par_insertion = function | [] -> []

| x :: reste -> ... tri_par_insertion reste;;

Il nous suffit maintenant de mettre l’´el´ement x `a la bonne place dans le reste maintenant tri´e de la liste. C’est facile : on se contente d’appeler la fonction ins`ere. Nous obtenons :

let rec tri_par_insertion = function | [] -> []

| x :: reste -> ins`ere x (tri_par_insertion reste);;

La fonction de tri est termin´ee. Il nous reste `a ´ecrire la fonction ins`ere. Par le mˆeme raisonnement que ci-dessus on commence par en ´ecrire le squelette :

let ins`ere ´el´ement = function | [] -> ...

| x :: reste -> ...;;

Le cas de la liste vide est encore une fois simple : il suffit de retourner une liste r´eduite `

a l’´el´ement qu’on souhaite ins´erer.

let ins`ere ´el´ement = function | [] -> [´el´ement]

| x :: reste -> ...;;

Dans l’autre cas, la liste o`u l’on veut ins´erer ´el´ement commence par x. Si ´el´ementest plus petit que x alors c’est le plus petit de tous les ´el´ements de la liste x :: reste, puisque celle-ci est tri´ee par hypoth`ese. On place donc ´el´ement au d´ebut de la liste x :: reste.

let ins`ere ´el´ement = function | [] -> [´el´ement]

| x :: reste -> if ´el´ement <= x then ´el´ement :: x :: reste else ...;;

Dans le cas contraire, c’est x le plus petit ´el´ement de la liste r´esultat ; ce r´esultat sera donc x :: ... Il nous reste `a ins´erer ´el´ement dans la liste reste. Un petit appel r´ecursif ins`ere ´el´ement resteet le tour est jou´e :

# let rec ins`ere ´el´ement = function | [] -> [´el´ement]

| x :: reste ->

if ´el´ement <= x then ´el´ement :: x :: reste

else x :: (ins`ere ´el´ement reste);; ins`ere : ’a -> ’a list -> ’a list = <fun>

Il nous reste `a d´efinir effectivement la fonction de tri et `a l’essayer :

# let rec tri_par_insertion = function | [] -> []

| x :: reste -> ins`ere x (tri_par_insertion reste);; tri_par_insertion : ’a list -> ’a list = <fun>

# tri_par_insertion [3; 2; 1];; - : int list = [1; 2; 3]

Synonymes dans les filtres

Pour am´eliorer la lisibilit´e du code de la fonction ins`ere, nous introduisons une facilit´e de nommage suppl´ementaire dans les filtres.

Il arrive que l’on veuille examiner la forme d’une valeur tout en nommant cette valeur. Consid´erez la fonction qui rend la valeur absolue d’un monˆome, repr´esent´e comme une paire d’entier (coefficient, degr´e) :

# let abs_mon^ome = function

(a, degr´e) -> if a < 0 then (-a, degr´e) else (a, degr´e);; abs_mon^ome : int * ’a -> int * ’a = <fun>

Ce code est parfaitement correct, mais dans le cas o`u le coefficient est positif on aimerait rendre directement le monˆome re¸cu en argument. Le code serait plus clair, puisqu’il n’y aurait pas besoin d’une petite gymnastique mentale pour se rendre compte que l’expression (a, degr´e) correspond exactement au filtre de la clause. Autrement dit, nous voudrions nommer mon^ome le filtre (a, degr´e) et rendre mon^ome quand a est positif. Dans ce cas, on introduit le nom choisi avec le mot-cl´e as (qui se prononce

«ase » et signifie « en tant que » en anglais).

Synonymes dans les filtres ::= filtre as nom Nous obtenons :

# let abs_mon^ome = function

(a, degr´e) as mon^ome -> if a < 0 then (-a, degr´e) else mon^ome;; abs_mon^ome : int * ’a -> int * ’a = <fun>

Maintenant le nommage indique `a l’´evidence qu’aucune transformation n’est faite sur le monˆome, alors que l’expression (a, degr´e), bien qu’´equivalente, cache un peu qu’elle n’est autre que l’argument de la fonction.

Pour la fonction ins`ere, l’usage d’un filtre synonyme pour nommer la liste argument clarifie ´egalement un peu le code :

# let rec ins`ere ´el´ement = function | [] -> [´el´ement]

| x :: reste as l ->

if ´el´ement <= x then ´el´ement :: l

else x :: (ins`ere ´el´ement reste);; ins`ere : ’a -> ’a list -> ’a list = <fun>

Fonctionnelles simples sur les listes 81

G´en´eralisation du tri `a tout type d’ordre

Pour g´en´eraliser la fonction de tri `a toute sorte d’ordres, il suffit de passer la fonction de comparaison en argument, comme on l’a vu au chapitre 4. Les fonctions ins`ere et tri_par_insertionprennent alors un argument suppl´ementaire, ordre, qu’on utilise pour comparer les ´el´ements, `a la place de la comparaison <=.

# let rec ins`ere ordre ´el´ement = function | [] -> [´el´ement]

| x :: reste as l ->

if ordre ´el´ement x then ´el´ement :: l else x :: (ins`ere ordre ´el´ement reste);;

ins`ere : (’a -> ’a -> bool) -> ’a -> ’a list -> ’a list = <fun> # let rec tri_par_insertion ordre = function

| [] -> []

| x :: reste -> ins`ere ordre x (tri_par_insertion ordre reste);; tri_par_insertion : (’a -> ’a -> bool) -> ’a list -> ’a list = <fun>

La mˆeme fonction nous permet maintenant de trier indiff´eremment des listes de chaˆınes ou de nombres, `a l’endroit ou `a l’envers :

# tri_par_insertion (function x -> function y -> x <= y) [3; 1; 2];; - : int list = [1; 2; 3]

# tri_par_insertion (function x -> function y -> x >= y) [3; 1; 2];; - : int list = [3; 2; 1]

# tri_par_insertion (function x -> function y -> ge_string x y) ["Salut "; "les "; "copains!"];;

- : string list = ["les "; "copains!"; "Salut "]

# tri_par_insertion (function x -> function y -> le_string x y) ["Salut "; "les "; "copains!"];;

- : string list = ["Salut "; "copains!"; "les "]

Remarque de complexit´e : on d´emontre que ce tri est quadratique (O(n2)) en moyenne (sur un jeu de donn´ees tir´ees au hasard). Dans le pire des cas, c’est-`a-dire quand le jeu de donn´ees n´ecessite le plus d’op´erations (ce qui correspond pour ce tri `a une liste tri´ee en ordre inverse), le tri par insertion est ´egalement quadratique. En revanche, il est lin´eaire pour une liste d´ej`a tri´ee.