1 Principe 2 Choix du pivot 3 Partition

publicité
L2S3 Harmonisation
UFR d’IEEA – Université Lille 1
Algorithmique
2015–2016
TP : Tri Rapide
Dans ce TP vous pouvez utiliser les fonctions données dans le fichier permutation.py
disponible sur le portail.
1
Principe
Le tri rapide, ou quicksort, est fondé comme le tri par fusion sur le paradigme "diviser
pour régner". C’est en effet également un algorithme récursif qui procède en divisant le
tableau en deux sous-tableaux, les tri (récursivement) et recombine les deux parties.
La différence entre ces deux algorithmes de tri réside dans la manière de diviser le
tableau puis de recombiner. Contrairement au tri par fusion, l’étape la plus coûteuse du
tri rapide, donc celle où s’effectue tout le travail, est la division en deux sous tableaux.
La recombinaison et immédiate et ne nécessite aucun travail.
De plus le tri rapide est un tri "en place" ou "sur place" : un espace mémoire constant
en plus du tableau à trier est suffisant pour effectuer ce tri.
De manière plus détaillée le principe du tri rapide est le suivant :
1. Diviser/Partitionner : Un élément du tableau est choisi arbitrairement, on appel
cet élément le pivot. La division consiste à placer tous les éléments de valeur inférieure à celle du pivot au début du tableau et tous les supérieurs en fin de tableau.
Les éléments de valeur égale au pivot se trouvent au milieu. Le tableau est donc
divisé en trois parties : les éléments inférieurs, égaux puis supérieurs au pivot.
2. Trier : Il s’agit simplement de trier récursivement les deux parties contenant les
éléments inférieurs et supérieurs au pivot.
3. Recombiner : Rien à faire, le tableau est déjà trié !
2
Choix du pivot
Comme vous le verrez dans la suite du TP choisir une bonne valeur de pivot est très
important pour avoir la meilleur complexité possible. Cependant choisir le pivot optimal
à chaque appel récursif nécessite presque autant d’opérations que pour trier le tableau,
c’est pourquoi on choisit en général une valeur aléatoirement dans le tableau.
Q1 . Ecrivez la fonction pivot retournant une valeur ( pas un indice !) choisie aléatoirement dans le tableau t entre les indices d et f , où t, d et f sont donnés en paramètre.
3
Partition
Il y a plusieurs méthodes pour écrire un algorithme itératif de partition, l’une d’elles
consiste à maintenir quatre zones délimitées par trois indices, a, b et c, comme illustré sur
la Figure 1 :
1. les éléments inférieurs au pivot se trouvent aux indices strictement inférieurs à a,
1
2. les éléments égaux au pivot se trouvent entre les indices a et c − 1,
3. les éléments supérieurs au pivot sont aux indices strictement supérieurs à b,
4. les éléments restants entre les indices c et b n’ont pas encore été comparés au pivot
et n’ont donc pas encore été placés dans l’une des trois précédentes parties.
a
< pivot
c
= pivot
b
???
> pivot
Figure 1 – Partition
L’idée est de comparer l’élément d’indice c au pivot puis de l’insérer dans l’une des
trois parties, inférieur, supérieur ou égale au pivot, suivant sa valeur.
3.1
La fonction partition
Q2 . Donnez les conditions vérifiées par les indices a, b et c lorsque la partition est
terminée, c’est-à-dire lorsqu’il ne reste plus d’éléments qui n’ont pas été comparés au
pivot.
Q3 . Quel est, en fonction (éventuellement) de a, b et c, le plus grand indice x d’un
élément strictement inférieur au pivot à la fin de l’algorithme de partition ?
Q4 . Quel est, en fonction (éventuellement) de a, b et c, le plus petit indice y d’un élément
strictement supérieur au pivot à la fin de l’algorithme de partition ?
Q5 . Donnez les valeurs initiales des indices a, b et c en fonction des indices d et f de
début et fin de tableau, c’est-à-dire lorsqu’aucun élément n’a encore été comparé au pivot.
Q6 . Ecrivez la fonction partition qui partitionne selon valeur p du pivot les éléments
du tableau t compris entre les indices d et f en trois parties : éléments inférieurs, égaux et
supérieurs au pivot. Cette fonction prend en paramètre t, d, f et p et retourne le couple
(x,y) d’indices où x est le plus grand indice d’un élément strictement inférieur au pivot
et y est le plus petit indice d’un élément supérieur au pivot à la fin de l’algorithme de
paritionnement.
Q7 . Quelle propriété intéressante pour des éléments au cours d’un algorithme de tri est
vérifiée par tous les éléments de valeur égale au pivot lorsque la partition est terminée ?
3.2
Complexité
Q8 . L’algorithme a-t-il des cas pire ou meilleur que les autres en terme de nombre de
comparaisons effectuées entre le pivot et les autres éléments du tableau ? Si oui donnez
ces cas, sinon expliquez pourquoi.
Q9 . Combien de comparaisons entre le pivot et les autres éléments du tableau t entre
les indices d et f sont effectuées ? Vous pouvez justifier votre réponse par un argument
(convaincant) ou un calcul.
2
Q10 . Donnez l’ordre de grandeur asymptotique du nombre de comparaisons.
4
Tri récursif
Toute la difficulté du tri se trouve dans la partition du tableau et comme pour le
tri par fusion, une fois la partie délicate de l’algorithme encapsulée dans une fonction
(partition ici) l’algorithme récursif de tri ne prend plus que quelque lignes à écrire.
4.1
La procédure tri_rapide
Q11 . Ecrivez la procédure tri_rapide prenant en paramètre un tableau t et deux
indices d et f de début et fin de tableau et qui effectue un tri rapide sur t. Vous pouvez
naturellement utiliser les fonctions pivot et partition écrites précédemment.
Q12 . Justifiez rapidement que l’algorithme effectue bien un tri du tableau. Vous pouvez
pour cela utiliser la propriété vérifiée par les éléments égaux à la valeur du pivot à la fin
de la fonction partition.
4.2
Complexité
Q13 . Donnez l’équation de récurrence décrivant le nombre de comparaisons entre éléments du tableau effectuées lors de l’exécution de la procédure tri_rapide sur un tableau
de taille n. Dans un premier temps on pourra considérer qu’il y a nI éléments inférieurs
et nS éléments supérieurs au pivot.
De plus, dans la suite on supposera qu’une valeur n’apparaît qu’une seule fois dans le
tableau et donc qu’il ne peut pas y avoir plusieurs cases contenant une valeur égale au
pivot.
Q14 . Que devient l’équation de récurrence s’il y a toujours autant d’éléments inférieurs
que d’éléments supérieurs au pivot ?
Q15 . Résoudre l’équation dans ce cas là et donner un ordre de grandeur asymptotique
du nombre de comparaisons que le tri effectue.
Q16 . Que devient l’équation de récurrence si le pivot est toujours l’élément maximum
ou minimum du tableau ?
Q17 . Résoudre l’équation dans ce cas là et donner un ordre de grandeur asymptotique
du nombre de comparaisons que le tri effectue.
Q18 . Déduire de vos résultats précédents un pire et un meilleur cas pour l’algorithme
du tri rapide.
Q19 . Le tri rapide est il intéressant pour un tableau presque trié ? Justifiez votre réponse.
Remarque : La complexité en moyenne du tri rapide, c’est-à-dire la moyenne du
nombre d’opérations sur toutes les données possibles de taille n (= tous les tableau
possibles de taille n), est en Θ(n log n). En pratique ce tri est généralement plus efficace
que le tri par fusion dont la complexité est toujours en Θ(n log n).
3
4.3
Preuve de correction
Prouver qu’un algorithme est correct consiste simplement à montrer que le calcul ou
le traitement qu’il effectue est bien celui pour lequel il a été écrit : à partir de données
vérifiant ses conditions d’utilisation il conduira toujours à un état final ou résultat qui
vérifie une propriété attendue.
Une preuve de correction est partielle si elle montre seulement que si l’algorithme
s’arrête alors il est correct. Il faut dans ce cas si possible la compléter par une preuve de
terminaison, c’est-à-dire montrer que l’algorithme va effectivement s’arrêter.
La logique de Hoare permet à partir d’une propriété vraie en début d’algorithme,
comme une condition d’utilisation, de déduire des propriétés vérifiées à chaque étape,
c’est-à-dire entre chaque calculs élémentaires, puis en fin de l’algorithme. Ecrire une preuve
compléte à la main est en général fastidieux.
Cependant la mise en évidence d’invariant de boucle permet souvent de se convaincre
qu’un algorithme itératif est correct. Un invariant de boucle est une propriété qui est
vérifiée après la phase d’initialisation, reste vraie à l’issue de chaque itération et qui
conjointement avec la condition d’arrêt de la boucle permet de montrer que le résultat
obtenu à la fin de la boucle est bien celui qui est attendu.
Q20 . Proposez un invariant (de boucle) pour (l’unique boucle de) l’algorithme de partitionnement.
Q21 . Votre invariant est-il vrai au début de la première itération ?
Q22 . Expliquer pourquoi si l’invariant est vérifié en début d’une itération il sera toujours
vrai au début de l’itération suivante.
Q23 . Donnez une preuve de correction informelle et partielle de l’algorithme.
Q24 . En observant ce qu’il se passe à chaque itération, justifiez que l’algorithme de
partionnement va bien se terminer.
4
Téléchargement