Algorithme de tris

publicité
MP1 Lycée Janson de Sailly
Algorithmes de tri
On note A[0 ...n − 1] un tableau contenant n éléments qui peuvent
être des entiers, des réels ou, plus généralement, toute collection de
valeurs sur lesquelles on peut définir une relation d’ordre total. En
pratique nous allons étudier des tableaux d’entiers positifs.
Algorithmes de tri
Table des matières
1 Tri par insertion
1
2 Tri
2.1
2.2
2.3
fusion
Principe . . . . . . . . . . . . . . . . . . . . . . . . . .
Algorithme de fusion . . . . . . . . . . . . . . . . . . .
Algorithme du tri par fusion . . . . . . . . . . . . . . .
2
2
2
4
3 Tri rapide (quick sort)
3.1 Algorithme du pivot . . . . . . . . . . . . . . . . . . .
3.2 Algorithme de tri rapide . . . . . . . . . . . . . . . . .
5
5
6
4 Application : recherche de la médiane dans une liste
7
On peut accéder à chaque élément du tableau au moyen de son
indice : entier i ∈ [[0...n - 1]]. A[i] désigne le (i + 1)ème élément du
tableau. A[0] est le premier élément du tableau et A[n − 1] le dernier.
La convention adoptée pour les indices est celle que Python utilise
pour les listes ou les tableaux numpy.
1
Tri par insertion
Cet algorithme s’inspire du tri que fait un joueur de cartes lors de
la distribution du jeu. Les cartes qu’il a en main sont déjà triées, la
plus petite étant à gauche et la plus grande à droite. Lorsqu’on lui
distribue une nouvelle carte, il va l’insérer à sa place en la comparant
aux plus grandes valeurs de son jeu.
But : étudier trois algorithmes qui permettent de trier les différents
éléments d’un tableau.
Supposons que la prochaine "carte" à insérer à la bonne place soit
A[i]. On suppose que le sous-tableau A[0 ...i − 1] est déjà trié.
• L’idée est de créer un trou à l’emplacement où se trouve A[i]
en la sauvegardant temporairement dans une variable temp.
• On compare A[i − 1] et A[i]. Si A[i − 1] 6 A[i] c’est terminé
et on remet temp à sa place. Si A[i − 1] > A[i] on décale
le contenu A[i − 1] dans la case libre A[i], ce qui déplace le
trou vers la gauche du tableau. On recommence en comparant
A[i − 2] avec A[i]. Le processus s’arrête lorsque on a trouvé k
tel que A[i − k] 6 A[i] ou si on atteint A[0].
• On recommence tout le processus avec la nouvelle carte à insérer, c’est à dire A[i + 1].
1
MP1 Lycée Janson de Sailly
Précondition : n > 1
Algorithmes de tri
# Au moins 2 éléments dans le tableau
2
fonction TRI_INSERTION(A, n) :
1.
2.1
pour i allant de 1 à n − 1 faire :
2.
temp = A[i]
3.
j=i
4.
tant que { j > 0 et A[j − 1] > temp } faire :
A[j] = A[j − 1]
6.
j =j−1
7.
A[j] = temp
Intérêt : Le tri se fait sur place c’est à dire à l’intérieur même du
tableau. Pas besoin de réserver un espace mémoire supplémentaire.
2.2
1. Écrire cet algorithme en langage Python et le tester avec la liste
proposée sur le site mp1.
Dans notre cas, les deux jeux de cartes sont en fait un sous-tableau
A[p...q, q + 1, ...r] de A, où p 6 q < r. Le premier jeux de carte est
représenté par A[p...q] et le second jeu par A[q + 1, ..., r]. Chacun est
déjà trié séparément, la plus petite valeur étant celle d’indice le plus
bas et la plus grande valeur celle d’indice le plus élevé. On aura donc :
2. En vous aidant du tableau ci-dessous, calculer la complexité
temporelle de cette fonction dans le meilleur des cas, puis dans
le pire des cas (à définir).
1
2
3
4
5
6
7
pour i allant de 1 à n-1 :
temp = A[i]
j=i
tant que ( j > 0 et A[j - 1] > temp ) :
A[j] =A[j - 1]
j=j-1
A[j] = temp
Coût
Algorithme de fusion
L’algorithme le plus important de ce tri est celui qui permet la
fusion de deux jeux de cartes triés séparément pour n’en former qu’un
dans lequel toutes les cartes sont triées.
Réalisation
fonction TRI_INSERTION(A, n)
Principe
Le tri fusion est un exemple du paradigme diviser pour résoudre.
Cela consiste à prendre un problème et à le diviser en deux sous - problèmes plus simples à résoudre. On utilise ensuite les deux solutions
pour résoudre le problème initial
Prenons l’exemple d’un jeu de 32 cartes à trier. L’idée est de partager ce jeu en deux jeux de 16 cartes (diviser) et de trier séparément
ces deux jeux (résoudre). On ré-assemble ensuite chacun des deux
jeux, grâce à un algorithme de fusion.
# j est l’indice du trou dans le tableau
5.
Tri fusion
A[p] 6 A[p + 1] 6 ... 6 A[q]
Nbre de fois
et
A[q + 1] 6 ... 6 A[r]
L’algorithme de fusion est écrit dans une fonction FUSION qui
prend en argument le tableau A et les trois entiers p, q et r :
c1
c2
c3
• On commence par "vider" A[p...r] dans deux sous-tableau : G
pour tous les éléments allant de A[p] à A[q] et D pour tous les
éléments allant de A[q+1] à A[r].
• On remplit ensuite à nouveau A[p...r] en comparant chaque élément de G et de D afin de le placer à la bonne place dans
A.
c4
c5
c6
c7
2
MP1 Lycée Janson de Sailly
Algorithmes de tri
fonction FUSION(A, p, q, r) :
Variables locales : tableaux d’entiers G[0...q − p + 1] , D[0...r − q]
1 pour i allant de 0 à q − p faire :
2
G[i] = A[p + i]
3 pour j allant de 0 à r − q − 1 faire :
4
D[j] = A[q + j + 1]
5 G[q − p + 1] = ∞
6 D[r − q] = ∞
7 i=0
8 j=0
9 pour k allant de p à r faire :
10
si G[i] 6 D[j] faire :
11
A[k] = G[i]
12
i=i+1
13
sinon :
14
A[k] = D[j]
15
j =j+1
Réalisation :
1. Écrire la fonction FUSION en Python
2. À l’aide du tableau ci-dessous, montrer que sa complexité temporelle T(n), avec n = r − p + 1 (nombre d’éléments du sous
tableau A[p...r] est O(n).
fonction FUSION(A, p, q, r) :
Aux lignes 5 et 6, on a recours à une astuce qui consiste à placer
une valeur appelée ∞ à la fin des tableaux G et D : en pratique, c’est
une valeur strictement supérieure à toutes les valeurs du tableau A
qui fait office de "butée" ou encore de sentinelle. Cela évite d’avoir à
introduire un test au sein de la boucle "pour" destiné à savoir quand
on atteint la fin d’un de ces deux tableaux.
1
2
pour i allant de 0 à q − p :
G[i] = A[p + i]
c1
c2
3
4
pour j allant de 0 à r − q :
D[j] = A[q + j + 1]
c3
c4
5
6
7
8
G[q − p + 1] = infini
D[r − q] = infini
i=0
j=0
c5
c6
c7
c8
9
pour k allant de p à r :
c9
10
11
12
13
14
15
Les lignes 7, 8 et 9 introduisent 3 entiers i, j et k qui sont les
indices des tableaux G, D et A. Pour chaque valeur de k entre p et
q, on compare G[i] et D[j] et on place la plus petite des deux valeurs
dans A[k].
On augmente ensuite i ou j suivant la valeur qui a été transférée.
Le processus est réitéré pour toutes les valeurs de k variant de p à r,
grâce à la boucle pour, ligne 9.
3
Coût
si G[i] 6 D[j] :
A[k] = G[i]
i=i+1
sinon :
A[k] = D[j]
j =j+1
c10
c11
c12
0
c14
c15
Nbre de fois
MP1 Lycée Janson de Sailly
2.3
Algorithmes de tri
Le tri du tableau entier se fait en appelant la fonction
TRI_FUSION(A, 0, n − 1).
Algorithme du tri par fusion
Soit à trier le sous-tableau A[p...r] (p 6 r) extrait de A[0...n − 1].
Réalisation :
La fonction TRI_FUSION, prend en paramètres le tableau A et les
deux entiers p et r. Elle utilise le paradigme "diviser pour résoudre",
c’est à dire qu’elle va séparer A[p...q] en deux tableaux de tailles à
peu près identique qu’elle va trier séparément, avec une approche
purement récursive.
1. Écrire cette fonction en Python et la tester sur la liste fournie
sur le site mp1.
2. Dessiner l’arbre des appels récursifs de la fonction TRI_FUSION
pour A = [7, 3, 1, 8, 4].
3. En vous aidant du tableau ci-dessous et en supposant que n =
r − p + 1 est toujours divisible par 2 (hypothèse simplificatrice
mais efficace), montrer que sa complexité temporelle T (n) vérifie
l’équation (approchée) :
fonction TRI_FUSION(A, p, r) :
1. si p < r faire :
p+r
2.
q=E
2
3.
TRI_FUSION(A, p, q)
4.
TRI_FUSION(A, q + 1, r)
5.
FUSION(A, p, q, r)
T (n) = 2 T
+ O(n)
T (2p )
, résoudre
2p
cette équation de récurrence et montrer que T (n) = O(n lg(n)
) où lg est le logarithme en base 2.
4. En posant n = 2p et en étudiant la suite up =
La ligne 1 teste si p < r. Dans le cas contraire, on a soit une
absurdité (r < p), soit il n’y a rien à trier (p == r) puisque le soustableau ne renferme alors qu’un seul élément.
1
2
3
4
5
p+q
(partie entière) ce
2
qui permet de diviser A[p...r] en deux sous tableaux de tailles égales
ou environ égales : A[p..q] et A[q + 1...r] : diviser.
À la ligne 2, on définit l’entier q = E
n
2
La fonction s’appelle ensuite de façon récursive lignes 3 et 4 pour
trier les deux sous-tableaux obtenus : résoudre.
À la ligne 5, on ré-assemble enfin les deux sous-tableaux triés grâce
à la fonction FUSION étudiée à la section précédente.
4
fonction TRI_FUSION(A, p, r) :
Coût
si p < r :
q = E( (p + r)/2 )
TRI_FUSION(A, p, q)
TRI_FUSION(A, q + 1, r)
FUSION(A, p, q, r)
c1
c2
Nbre de fois
MP1 Lycée Janson de Sailly
3
Algorithmes de tri
La fonction PIVOT ci-dessous permet de réordonner les éléments
A[p], A[p + 1], ..., A[q] en plaçant toutes les valeurs inférieures au
pivot à gauche de celui-ci et toutes les valeurs supérieures au pivot
à droite. Elle retourne l’indice ind_p du pivot, après que A[p...q] ait
été ré-ordonné.
Tri rapide (quick sort)
Le tri rapide est très souvent utilisé, bien que ce ne soit pas le
meilleur en terme de complexité temporelle. Comme le tri par fusion,
il fonctionne lui aussi sur le principe diviser pour résoudre et sur
l’approche purement récursive. En voici le principe :
fonction PIVOT(A, p, q) :
1 si p < q :
2
pivot = A[q]
3
temp = A[p]
4
min = p
5
max = q
6
tant que min < max :
7
si temp > pivot :
8
A[max] = temp
9
max = max - 1
10
temp = A[max]
11
sinon :
12
A[min] = temp
13
min = min + 1
14
temp = A[min]
15
A[min] = pivot
16
retourner min
17 sinon :
18
retourner −1
• On commence par choisir une valeur particulière du tableau
qu’on appelle le pivot. On ré-ordonne ensuite le tableau en
plaçant toutes les valeurs < pivot à gauche de celui-ci et toutes
les valeurs > pivot à droite de celui-ci. L’algorithme PIVOT
se charge de faire cela.
• À la fin de l’algorithme précédent, le pivot est à la bonne place
dans le tableau. Soit ind_p son indice.
• Le tableau A est ensuite divisé en deux sous-tableaux
A[0...ind_p −1] et A[ind_p + 1...n − 1] à qui on va appliquer
séparément l’algorithme PIVOT : on recommence le premier
point avec chacun des deux sous-tableaux, selon une approche
récursive.
3.1
Algorithme du pivot
Prenons un tableau A[0...n − 1] contenant n > 1 éléments entiers
positifs que l’on souhaite trier. Pour des raisons qui vont apparaître
plus tard, on va commencer par s’intéresser au sous-tableau A[p...q]
délimité par les indices p et q, vérifiant :
06p<q 6n−1
À la ligne 1, on place une condition p < q pour lancer l’algorithme.
En effet, si p > q, c’est absurde et si p == q, A ne contient qu’un
seul élément et il n’est pas nécessaire de le réordonner. Dans ce cas
l’indice retourné vaut −1 : cette valeur ne pouvant pas être celle d’un
indice, cela permet de placer une sentinelle dans le programme.
On choisit comme pivot de ce sous tableau A[p...q] son dernier
élément A[q].
Ligne 2, le pivot choisi est toujours A[q], valeur de plus haut indice et on crée un trou dans A en sauvegardant cette valeur dans la
5
MP1 Lycée Janson de Sailly
Algorithmes de tri
fonction PIVOT(A, p, q)
variable pivot. À la ligne 3, on crée un deuxième trou dans A en
sauvegardant temporairement A[p] dans une autre variable, nommée
temp.
1
2
3
4
5
Les indices des deux trous sont appelés min et max. Ces indices
vont varier au cours de la progression de l’algorithme. Au début, min
vaut p et max est égal à q (initialisation).
À partir de la ligne 6, on s’engage dans une boucle qui ne va s’arrêter
que lorsque min sera égal à max, avec deux options : si ... sinon ...
6
7
8
9
10
11
12
13
14
15
16
• Si temp > pivot, on place la valeur contenue dans temp dans
A[max] et on diminue max d’une unité : le trou indicé par
max se déplace donc vers la gauche. On crée alors un nouveau
trou en max-1 en sauvegardant la valeur dans temp.
• Au contraire, si temp 6 pivot, on place la valeur contenue dans
temp dans A[min] puis on augmente min d’une unité : le trou
se déplace donc vers la droite. On crée alors un nouveau trou
en min + 1 en sauvegardant la valeur dans temp.
17
18
À la ligne 15, tous les éléments du tableau sont correctement positionnés et il reste un seul trou d’indice
min == max. Il ne reste plus qu’à remettre l’élément pivot à
cette position.
3.2
La fonction retourne alors l’indice du pivot (ou −1).
Coût
si p < q :
pivot = A[q]
temp = A[p]
min = p
max = q
tant que min < max :
si temp > pivot :
A[max] = temp
max = max - 1
temp = A[max]
sinon :
A[min] = temp
min = min + 1
temp = A[min]
A[min] = pivot
retourner min
sinon :
retourner −1
Nbre de fois
c1
c2
c3
c4
c5
c6
c7
c8
c9
c10
0
c12
c13
c14
c15
c16
0
c18
Algorithme de tri rapide
Voici maintenant l’algorithme du tri rapide. Il est écrit sous la forme
d’une fonction TRI_RAPIDE(A, p, q) qui prend comme paramètres
un tableau A[0...n − 1] et les deux entiers p et q qui délimitent le
sous-tableau A[p...q] qui va être trié.
Réalisation :
1. Écrire la fonction PIVOT en Python.
2. En vous aidant du tableau ci-contre, montrer que sa complexité
temporelle T (n) où n = q − p + 1 est le nombre d’éléments du
sous-tableau A[p...q] vérifie T (n) = O(n).
• À la ligne 1, la fonction PIVOT est appelée. Elle ré-ordonne
A[p...q] et place le pivot à la bonne position : tous les éléments
de A[p...q] inférieurs au pivot sont à sa gauche et tous ceux qui
6
MP1 Lycée Janson de Sailly
Algorithmes de tri
L̂ = [x̂0 , ..., x̂n−1 ] formée des mêmes éléments écrits dans l’ordre croissant.
sont supérieurs au pivot sont à sa droite.
• On récupère l’indice du pivot dans la variable ind_p.
• Lorsque n = 2p + 1 est impair, la valeur médiane est la valeur
x̂p pour laquelle il existe p éléments strictement plus petits et p
éléments strictement plus grands.
• Lorsque n = 2p est pair, on appelle valeurs médianes les deux
éléments x̂p−1 et x̂p .
• Il ne reste plus qu’à trier les deux sous-tableaux A[p...ind_p −1]
et A[ind_p + 1...q], ce qui est fait aux lignes 3 et 4 en appelant
la fonction TRI_RAPIDE de façon récursive.
fonction TRI_RAPIDE(A, p, q) :
1
2
3
4
Réalisation : considérons une liste A non triée composée de n éléments tous distincts deux à deux. Écrire une fonction MEDIANE(A) qui
retourne la valeur ou les valeurs médianes de A et dont la complexité
soit en O(n lg n).
ind_p = PIVOT(A, p, q)
si ind_p != −1 :
TRI_RAPIDE(A, p, ind_p −1)
TRI_RAPIDE(A, ind_p + 1, q)
Si vous voulez trier le tableau entier A[0.. n − 1] tout entier, il suffit
d’appeler la fonction TRI_RAPIDE(A, 0, n − 1).
Réalisation :
1. Écrire cette fonction en Python et vérifier son fonctionnement
sur la liste exemple du site mp1.
2. Dessiner l’arbre des appels récursifs lorsque A = [7, 3, 1, 8, 4].
On peut montrer que la complexité temporelle T (n) de cette fonction est O(n2 ) dans le pire des cas et O(n lg(n) ) dans le meilleur des
cas.
4
Application : recherche de la médiane dans
une liste
Soit L = [x0 , ..., xn−1 ] une liste de n valeurs distinctes appartenant à un ensemble totalement ordonnée. On lui associe la liste triée
7
Téléchargement