Informatique/DS/DS2/DS2-Tri

publicité
DS
NOM, Prénom : Brillant Dany
NOTE : 20/20 Youhou !!
DS d’informatique n°2 (Corrigé)
EXERCICE 1 : FONCTION MYSTERE
Soit le programme suivant :
def tri_mystere(L): # L une liste d’entiers et
n = len(L)
for i in range(1, n):
j = i
x = L[i]
while 0 < j and x < L[j-1]:
L[j] = L[j-1]
j = j-1
de flottants
L[j] = x
Question 1 : (2 pt) Lors de l'appel tri_mystere(L), lorsque L est la liste [5, 2, 3, 1,
4], donner le contenu de la liste L à la fin de chaque itération de la boucle for.
A la fin de l’itération
Contenu de L
0 (avant les
itérations)
[5, 2, 3, 1, 4]
1
[2, 5, 3, 1, 4]
2
[2, 3, 5, 1, 4]
3
[1, 2, 3, 5, 4]
4
[1, 2, 3, 4, 5]
Question 2 : (1 pt) Quel type de tri est utilisé dans la fonction tri_mystere(L) ? Justifier
votre réponse.
C’est du tri par insertion, car à chaque itération de la boucle for, l’élément d’indice i , est inséré
au bon endroit dans la sous-liste L[0:i] .
Question 3 : (3 pt) Montrer, par l’invariant de boucle, que la fonction permet bien de trier la
liste.
Invariant de boucle : Inv(i) = « à la fin d’une l’itération de la boucle for, la sous-liste
L[0:i+1] est trié ».
Démo :
•
•
Initialisation : Avant de rentrer dans la boucle, la sous-liste L[0:1]= L[0] est
forcément triée car composée d’un seul élément.
Hérédité : supposons Inv(i) vrai. A l’itération suivante, la boucle while ne s’arrête que
si
Lycée Buffon
1/6
DS 2 (1h15)
§
x >= L[j-1]: et x
s’insère entre deux éléments inférieurs et
supérieurs, donc bien triée
§
soit
jatteint 0 et x est placé en tout début de liste car inférieur à tous
les éléments qui le précèdent.
Donc la sous-liste L[0:i+2]est triée, donc Inv(i+1) vrai.
•
•
Terminaison : Un boucle for s’arrête forcement, et la boucle while s’arrête également
car j diminue à chaque passage, et donc va forcément atteindre 0, dans le pire des
cas.
Bonne terminaison : Les itérations s’arrêtent pour i=n-1, donc Inv(n-1) vrai, soit la
sous-liste L[0:n] , c’est à dire la liste L est triée, CQFD.
Question 4 : (3 pt) Soit n, le nombre d’éléments dans la liste L. Donner, en le démontrant, et
en précisant à quelle forme de L cela correspond, la complexité algorithmique de la fonction
tri_mystere(L) dans le meilleur et le pire des cas :
Meilleur des cas :
-
L est déjà triée
Complexité (à démontrer):
Prennons comme critère de calcul de la complexité, le nombre de comparaisons.
Chaque test de la boucle while est faux, donc il n’y a globalement qu’une boucle for, il y a
donc n-1 itérations, donc 2(n-1) comparaisons, la complexité algorithmique est donc en O(n).
Pire des cas :
-
L est triée par ordre décroissant
Complexité (à démontrer):
Chaque test de la boucle while est vrai, jusqu’à ce que j atteigne 0.
Pour i= 1 : 1 passage dans les instructions de la boucle while (2 comparaisons)
Pour i= 2 : 2 passages dans les instructions de la boucle while (4 comparaisons)
…
Pour i = n-1 : n-1 passage dans les instructions de la boucle while (2(n-1) comparaisons)
Donc le nombre de passage total, noté Nit , dans les instructions de la boucle while est de :
!!!
𝑁!" = 1 + 2 + ⋯ + 𝑛 − 1 =
𝑖=
!!!
(𝑛 − 1)𝑛
2
Il y a donc (𝑛 − 1)𝑛 comparaisons, donc une complexité algorithmique en O(n2).
Question 5 (question de cours) (3 pt): Pour les trois algorithmes de tri suivants, donner la
complexité temporelle pour les deux types de listes proposées.
Lycée Buffon
2/6
DS 2 (1h15)
Tri par Insertion
Tri Rapide
Tri fusion
Liste quasiment triée
O(n)
O(n2)
O(n.log(n))
Liste quelconque
O(n2).
O(n.log(n)) ou O(n2)
O(n.log(n))
EXERCICE 2 : OPTIMISATION DU TRI PAR INSERTION
On peut optimiser l’algorithme du tri par insertion, en recherchant par dichotomie la position
d’insertion.
Question 6 : (4 pt) Écrire une fonction ajouter(L,x) qui étant donnés une liste triée L et un
élément x, modifie L en insérant x dans la liste L à la bonne place (de sorte d’obtenir une liste
triée dans l’ordre croissant). On recherchera la position d’insertion par dichotomie.
Exemple : l’instruction ajouter([1,3,4],2) doit renvoyer la liste [1,2,3,4]
Remarque : on pourra utiliser la méthode L.insert(i,x) qui permet d’insérer l’élément x
dans la liste L dans la position i.
def ajouter(L,x):
a = 0
b = len(L)
if x>L[-1]:
L.append(x)
else:
while a<b:
m=(a+b)//2
if L[m]<=x:
a=m+1
else:
b=m
L.insert(a,x)
return L
Question 7 : (1 pt) Estimer, en justifiant brièvement, la complexité algorithmique de la fonction
ajouter() si on suppose que la fonction insert() a une complexité algorithmique en O(1).
C’est une algorithme de dichotomie, donc l’intervalle de recherche est divisé par 2 à chaque
itération, ce qui est typique d’une complexité algorithmique en O(log2(n)) ou O(log(n)).
A savoir redémontrer !
On donne la fonction Tri_Insertion(L) qui permet de trier une liste L par l’algorithme du
tri par insertion et qui utilise la fonction ajouter(L,x) définie précédemment.
def Tri_Insertion(L):
for i in range(1,len(L)):
SL = L[0:i] # sous-liste triee
x=L.pop(i)
ajouter(SL,x)
L[0:i] = SL
return L
Lycée Buffon
3/6
DS 2 (1h15)
Question 8 : (2 pt) Expliquer simplement pourquoi, si n est la longueur de la liste, alors la
complexité de cet algorithme est en O(n log(n)). On précise que :
Ø les méthodes "classiques" des listes comme pop, append... fonctionnent en temps
constant.
Ø on n'attend pas une démonstration mathématique rigoureuse, mais il faut tout de
même justifier votre réponse.
En déduire l'avantage et l'inconvénient de cette version du tri par insertion par rapport à
l’algorithme « classique » vu en cours.
La boucle for effectue n itérations.
A chacune d’elle, on appelle la fonction ajouter(L,x) sur une sous-liste de longueur i ; les autres
opérations s'effectuent en temps constant. Le coût du tour est donc en O(log(i)) (cf. question 6).
Le coût total de la fonction est donc en O(log(1) + log(2) + ... + log(n)) soit en O(n log(n)). Sans entrer
dans les détails mathématiques, cela peut se justifier simplement :
• soit par une comparaison série/intégrale, une primitive de ln étant la fonction x -­‐> xln(x) -­‐ x, • soit en remarquant que log(1) + ... + log(n) = log(n!), si l'on sait que n! se comporte comme nn (d'après la formule de Stirling par exemple).
Avantage : on évite le "pire cas" en O(n2) de l’algorithme classique (liste triée à l'envers).
Inconvénient : on évite le "meilleur cas" en O(n) de l'algorithme classique (liste triée).
Question 9 : (3 pt)Donner une version récursive, notée Tri_Insertion_REC(L) de la
fonction Tri_Insertion(L).
def Tri_Insertion_REC(L): # recursive
if len(L)==1:
return L
else:
x=L.pop()
Tri_Insertion_REC(L)
ajouter(L,x)
return L
Lycée Buffon
4/6
DS 2 (1h15)
EXERCICE 3 : TRI PAR SELECTION
L'algorithme du tri par sélection est le suivant :
Ø on recherche le plus petit élément de la liste à trier,
Ø on l'échange avec le premier élément,
Ø puis on répète le même procédé sur la "sous-liste" restante.
Question 10 : (3 pt)Ecrire une fonction Tri_Selection(L) qui utilise l’algorithme défini.
def tri_selection(L):
n = len(L)
# boucle sur l'elt a placer (sauf le dernier)
for i in range(n):
# recherche du minimum de la sous-liste
Lmin,jmin = L[i],i
for j in range(i+1,n):
if L[j] < Lmin:
Lmin,jmin = L[j],j
# echange
L[jmin],L[i] = L[i],L[jmin]
return L
Question 11 : (1,5pt) Estimer la complexité de cette fonction et comparer son efficacité par
rapport aux algorithmes de tri évoqués de la question 5.
Soit n la taille de la liste.
La fonction possède deux boucles imbriquées de longueur n, dont le contenu s'effectue en
temps constant. On a donc une complexité en O(n2).
Cet algorithme est moins efficace que les algorithmes évoqués dans l’exercice 2 ; au mieux, il est
aussi efficace. Si la liste est quasiment triée, même le tri par insertion est plus efficace !
On pourrait donc aussi l’appeler le tri_naze, le tri_mou, ou le tri_qu_est_pas_efficace.
Lycée Buffon
5/6
DS 2 (1h15)
BROUILLON :
Même pas besoin.
Lycée Buffon
6/6
Téléchargement