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.