Le tri par tas du cours est-il en place ?
Exercice · niveau 2 · informatique (MP2I/MPI), chapitre 11 — Arbres de recherche, tas et files de priorité
Énoncé
Le chapitre affirme que le tri par tas « se fait en place dans le tableau à trier ». Il donne aussi ce code :
let tri_par_tas t =
let f = tas_vide () in
Array.iter (fun x -> inserer f x) t;
Array.init (Array.length t) (fun _ -> extraire_min f)
Ce code est-il en place ? Repose-t-il sur une hypothèse cachée ?
Corrigé
Non, ce code n'est pas en place, et il faut le dire nettement : le chapitre décrit une méthode et en montre une autre.
Ce que ce code alloue. Un tas auxiliaire de éléments, puis un tableau résultat de éléments : de mémoire supplémentaire, et le tableau d'entrée t n'est pas modifié. C'est un tri hors place, exactement comme le tri par partition-fusion auquel le chapitre l'oppose. La version véritablement en place est celle du premier problème ci-après : on construit le tas dans le tableau lui-même, puis on échange la racine avec la dernière case, en de mémoire supplémentaire.
L'hypothèse cachée. Array.init n f n'a de sens ici que si f est appelée sur dans cet ordre : la fonction ignore son argument et lit un effet de bord, si bien qu'un ordre d'appel différent rendrait le tableau permuté, donc non trié. Mesuré sur la version installée (OCaml 5.4.1) : les appels ont bien lieu dans l'ordre croissant, et la documentation de Array.init le garantit — « tabulates the results of f applied in order to the integers 0 to n-1 ».
Mais c'est une garantie sur laquelle on ne devrait pas s'appuyer, parce que le lecteur ne peut pas la deviner : rien dans Array.init (Array.length t) (fun _ -> extraire_min f) ne dit que l'ordre compte. On préfère l'écriture qui le dit :
(* Renvoie un tableau trié contenant les mêmes éléments que t. t est inchangé.
Complexité : Theta(n log n) en temps, Theta(n) en mémoire supplémentaire. *)
let tri_par_tas_hors_place t =
let f = tas_vide () in
Array.iter (fun x -> inserer f x) t;
let n = Array.length t in
if n = 0 then [||]
else begin
let r = Array.make n t.(0) in
for i = 0 to n - 1 do
r.(i) <- extraire_min f (* l'ordre est ÉCRIT, pas supposé *)
done;
r
end
Le principe : quand la correction d'un code dépend d'un ordre d'évaluation, on écrit une boucle. Une boucle for rend l'ordre visible ; un Array.init le cache. La différence ne se voit pas à l'exécution — jusqu'au jour où elle se voit.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.