Академический Документы
Профессиональный Документы
Культура Документы
7 octobre 2011
prsent par e e
Matthieu Finiasz
Un probl`me concret e
Recherche de collisions
Le paradoxe des anniversaires dit que 365 l`ves sont susants ee (en moyenne) pour avoir une collision danniversaire, deux l`ves ayant leur anniversaire le mme jour. ee e Comment fait-on pour ecacement trouver ces paires dl`ves ? ee
Un probl`me concret e
Recherche de collisions
Le paradoxe des anniversaires dit que 365 l`ves sont susants ee (en moyenne) pour avoir une collision danniversaire, deux l`ves ayant leur anniversaire le mme jour. ee e Comment fait-on pour ecacement trouver ces paires dl`ves ? ee Mthode simple : e on remplit un tableau avec les n dates danniversaire, on compare chaque lment ` tous les autres du tableau ee a complexit en (n2). e Peut-on faire mieux ?
Un probl`me concret e
Recherche de collisions
Le paradoxe des anniversaires dit que 365 l`ves sont susants ee (en moyenne) pour avoir une collision danniversaire, deux l`ves ayant leur anniversaire le mme jour. ee e Comment fait-on pour ecacement trouver ces paires dl`ves ? ee Mthode simple : e on remplit un tableau avec les n dates danniversaire, on compare chaque lment ` tous les autres du tableau, ee a complexit en (n2). e Peut-on faire mieux ? OUI on trie le tableau, on le parcourt en regarant si 2 voisins sont gaux, e complexit en (n log n). e
Le tri
Description du probl`me e
On se donne un tableau de n lments (des entiers par exemple) et ee une relation dordre totale . on eectue un tri par comparaison, utilisant uniquement la complexit est le nombre de comparaisons. e ( 2) Les algorithmes lmentaires ont une complexit en n . ee e Les meilleurs algorithmes ont une complexit en (n log(n)). e
Complexit variable e
Quelques dnitions e
Complexit dans le pire cas e Temps de calcul dans le pire cas pour les entres de taille n xe : e e T (n) = max T (x).
{x,|x |=n}
Complexit moyenne e Temps de calcul moyen sur toutes les entres de taille n xe : e e Tm (n) = pn (x)T (x).
x,|x |=n
1 2 3 4 5 6 7 8 9 10 11 12
void insertion_sort(int* tab, int n) { int i,j,tmp; for (i=1; i<n; i++) { tmp = tab[i]; j = i-1; while ((j >= 0) && (tab[j] > tmp)) { tab[j+1] = tab[j]; j--; } tab[j+1] = tmp; } }
Llment i est rang parmi les i 1 premiers lments (dj` tris) : ee e ee ea e i insrer un lment cote au pire i en moyenne 2 , e e e ( )u complexit en n2 dans(le pire cas (tableau inversement tri), e e ) complexit moyenne en n2 aussi. e
Tri ` bulles a Lide est de faire remonter les grands lments ` la n du tableau e ee a on parcourt le tableau en comparant llment i au i + 1 et on ee les inverse si ncessaire, e ` la n du parcours, le dernier lment est le plus grand, a ee apr`s i parcours les i plus grands lments sont ` la n. e ee a on fait n 1 parcours et la tableau est tri. e Complexit : on eectue (n) parcours comportant (n) compae raisons ` chaque fois, a ( 2) le tri ` bulles a une complexit de n dans le pire cas et en a e moyenne. Pour amliorer un peu les performances, on arrte le tri d`s que e e e lun des parcours ninverse aucun lments. ee Ce tri ne fait que (n) comparaisons sur un tableau dj` tri. ea e
Tri fusion
Si a marche pour n , a marche pour n c c 2
Insrer un lments dans un tableau tri cote (n), e ee e u fusionner deux tableaux de taille n cote aussi (n). u 2
p (p+r)/2 r-1 1 5 4 9 7 3 6 2 1 5 4 9 1 5 1 5 4 9 4 9 7 3 6 2 7 3 7 3 6 2 6 2
On cherche ` rduire le probl`me : a e e on coupe le tableau en deux, on trie chaque moiti, e on fusionne. Le cot total est celui des fusions : u chaque fusion cote (r p), u le cot total est (n log(n)), u dans le pire cas et en moyenne.
1 5
4 9
3 7
2 6
1 4 5 9
2 3 6 7
1 2 3 4 5 6 7 9
Tri fusion
Fusion des sous-tableaux
1 4 5 9
2 3 6 7
1 4 5 9 7 6 3 2
puis on parcourt par les deux bouts en avanant du plus petit ct c oe ` chaque fois (cot (n) aussi). a u
4 5 9 7 6 i j 5 9 7 6 i j
1 2 3
1 2 3 4
Tri rapide
Quicksort
Tri rcursif bas sur un partitionnement : e e on choisit un pivot, on permute les lments du tableau ee les petits au dbut, puis le pivot, puis les grands, e on trie les petits entre eux et les grands entre eux.
1 2 3 4 5 6 7
void quick_sort(int* tab, int p, int r) { if (r-p > 1) { int q = partition(tab,p,r); quick_sort(tab,p,q); quick_sort(tab,q+1,r); } }
Tr`s rapide, peut tre fait en place :) e e ( 2 complexit dans le pire cas n , e complexit en moyenne (n log(n)). e
int partition(int* tab, int p, int r) { int x = tab[p]; int q = p; int i,tmp; for (i=p+1; i<r; i++) { if (tab[i] <= x) { q++; tmp = tab[q]; tab[q] = tab[i]; tab[i] = tmp; } } tmp = tab[q]; tab[q] = tab[p]; tab[p] = tmp; return q; }
Complexits compares e e
Asymptotiquement
Algorithme Tri par insertion Tri ` bulles a Tri rapide Tri fusion
Complexits compares e e
En pratique
En moyenne
temps
n Tableau tri
temps
temps
Application
Lalgorithme quickselect
Pour trouver la mdiane (ou le k-i`me lment) dun tableau : e e ee on peut trier le tableau et prendre llment n , ee 2 cote (n log n). u mais cela nest pas ncessaire... e
On sinspire du tri rapide : on conserve la ligne : int q = partition(tab,p,r); au lieu de 2 appels rcursifs, on nen fait quun e selon que q < n ou q > n , 2 2 on ne fait quune partie du tri, le minimum ncessaire, e la complexit moyenne est n + n + n + n + ... = 2n = (n). e 2 4 8
Un tri par comparaison prend en entre n lments e ee ne fait que des comparaisons {1,4,3} est identique ` {2,4,3} a n! entres possibles, selon lordre des lments. e ee Un algorithme de tri par comparaison fait une suite de comparaison, puis donne une permutation qui remet les lments en ordre ee se comporte diremment pour chacune des n! entres possibles. e e Un tel tri peut se reprsenter par un arbre binaire : e chaque nud est une comparaison en fonction du rsultat on descend dans le ls gauche ou droit e chaque feuille correspond ` une permutation a chaque entre aboutit ` une feuille dirente. e a e
a1:a2 < a2:a3 < 1,2,3 < 1,3,2 > a1:a3 > 2,3,1 2,1,3 < 3,1,2 < > a1:a3 > a2:a3 > 3,2,1
a1:a2 < a2:a3 < 1,2,3 < 1,3,2 > a1:a3 > 2,3,1 2,1,3 < 3,1,2 < > a1:a3 > a2:a3 > 3,2,1
Nombre de comparaison = longueur de la branche cet arbre reprsente un algorithme qui trie en 3 comparaisons. e
= n log n n log e
h est aussi le nombre moyen de comparaisons pour des entres quidistribues e e e la complexit moyenne dun tri est (n log n). e pour nimporte quelles entres e la complexit dans le pire cas est (n log n). e
Rcursivit e e
Algorithme rcursif e
Un concept utile
Un algorithme rcursif est un algorithme dni en rfrence ` e e ee a lui mme, et qui comprend une condition de terminaison. e Exemples : suite de Fibonacci, tri fusion, tri rapide... souvent faciles ` crire et ` comprendre ae a cest le compilateur qui fait le travail tr`s dirent dune fonction normale ! e e Permettent dutiliser une approche diviser pour rgner : e on divise le probl`me en sous-probl`mes e e on traite les sous-probl`mes (rcursivement ou directement) e e on recombine les rsultats. e
Les tours de Hano On cherche ` dplacer n disques de la tour 1 ` la tour 3 a e a il est interdit de poser un disque sur un plus petit que lui on dplace un seul disque ` la fois. e a
Les tours de Hano Puis on dplace le grand disque ` sa place. e a Cette tape est ncessaire dans tout solution. e e
Les tours de Hano On redplace rcursivement les n 1 petits disques vers la tour 3. e e
Les tours de Hano Et voila ! La magie de la rcursion a encore une fois opre... e ee on dcrit en quelques tapes un algorithme exponentiel en n. e e
1 2 3 4 5 6 7 8 9 10 11 12
void Hanoi(int n, int i, int j) { int intermediate = 6-(i+j); if (n > 0) { Hanoi(n-1,i,intermediate); printf("Mouvement de %d vers %d\n",i,j); Hanoi(n-1,intermediate,j); } } int main(int argc, char* argv[]) { Hanoi(atoi(argv[1]),1,3); }
T (n) le nombre de mouvements ncessaire pour dplacer n disques : e e T (0) = 0 et T (1) = 1, T (n) = 1 + 2 T (n 1). On obtient : T (n) = 2n 1 = (2n ). Ltape de mouvement du grand disque est ncessaire e e cette complexit est intrins`que au probl`me. e e e La complexit spatiale est (n) e on ne stocke rien, mais on a n appels rcursifs imbriqus. e e
Pour dplacer 50 disques, ` 1 disque par seconde, il faut environ 35 e a millions dannes. e
Tri fusion Calculer la complexit T (n) en nombre de comparaisons : e T (n) = D(n) + R(n) + C (n).
D(n) : cot pour diviser le probl`me en sous-probl`mes. D(n) = 0 u e e R(n) : cot pour rsoudre les sous-probl`mes. Ici, deux sousu e e probl`mes de taille n , donc R(n) = 2 T ( n ). e 2 2 C (n) : cot pour combiner les rsultats. Ici, une fusion : C (n) = n. u e n 2, T (n) = 2 T (n ) 2 +n
Tri rapide
1 2 3 4 5 6 7 8
void quick_sort(int* tab, int p, int r) { int q; if (r-p > 1) { q = partition(tab,p,r); quick_sort(tab,p,q); quick_sort(tab,q+1,r); } }
Tri rapide Pour une entre de taille n : e Division en deux sous-probl`mes de taille q 1 et n q, cot de e u partition : D(n) = n 1. Rsolution des sous-probl`mes : cot R(n) variable selon la valeur e e u de q. Combinaison des rsultats : cot C (n) = 0. e u 2 T (n) meilleur cas 2 n 1 cas moyen T (n) = n 1 + q=1 T (q 1) + T (n q) n T (n 1) pire cas On trouve les solutions : T (n) = (n log n) (meilleur cas et cas moyen), ( 2) T (n) = n (pire cas).
Fibonacci rcursif e
Complexit exacte e
1 2 3 4 5 6
on fait un changement de variable S(n) = T (n) + 1 qui donne S(n) = S(n 1) + S(n 2) et donc S(n) = (n ). Comme annonc prcdemment T (n) = (n ). e e e
Compilation dune fonction rcursive e Le code produit par un compilateur est une suite dinstruction avec des sauts, cest tout. Un if est un saut conditionnel : soit on excute linstruction suivante, soit on fait un saut un peu e plus loin.
execution
if
code du else
suite du code
Compilation dune fonction rcursive e Le code produit par un compilateur est une suite dinstruction avec des sauts, cest tout. Un appel ` une fonction peut-tre trait de deux faons : a e e c soit on copie le code de la fonction (cest de linline),
execution appel de f inline du code de f suite du code
?
code de f
Compilation dune fonction rcursive e Le code produit par un compilateur est une suite dinstruction avec des sauts, cest tout. Un appel ` une fonction peut-tre trait de deux faons : a e e c soit on copie le code de la fonction (cest de linline),
execution appel de f inline du code de f suite du code
soit on fait un saut vers le code de la fonction on utilise une pile dadresses de retour.
pile des appels
code de f
Compilation dune fonction rcursive e Le code produit par un compilateur est une suite dinstruction avec des sauts, cest tout. Une fonction rcursive ajoute successivement les adresses de retour e dans la pile : n appels imbriqus ont une complexit mmoire en (n). e e e
Autres remarques : un appel ` une fonction cote cher a u compromis entre inline/taille du code et temps dexcution, e certains vieux langages ne g`rent pas les fonctions rcursives : e e si on fait toujours de linline, les fonctions non rcursives sont e beaucoup plus simples ` compiler que les rcursives. a e
La rcursion terminale e
En anglais : tail recursion
La recursion terminale est un cas particulier de rcursion : e lappel rcursif est la derni`re commande excute, e e e e la valeur de retour de la fonction est la mme que celle retourne e e par lappel rcursif, e marche aussi sil ny a pas de valeur de retour. Il est alors possible de convertir la fonction rcursive en boucle : e un appel de fonction est toujours lourd (criture dans la pile...) e la rcursion terminale permet dtre plus ecace. e e Avec gcc, la rcursion terminale est prise en compte quand on e utilise loption doptimisation -O2 (ou -O3).
Rcursion terminale e
Exemple de lalgorithme dEuclide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
int euclide(int x, int y) { if (y == 0) { return x; } else { return euclide(y,x % y); } } int euclide_iter(int x, int y) { int tmp1,tmp2; while (!(y == 0)) { tmp1 = y; tmp2 = x % y; x = tmp1; y = tmp2; } return x; }
Ce quil faut retenir de ce cours Il existe beaucoup dalgorithmes dirents pour trier un tableau : e les plus basics cotent (n2), u tri par insertion, tri ` bulles, tri cocktail... a les meilleurs cotent (n log n), u tri fusion, tri rapide, tri par tas, tri par arbre...
La rcursivit permet de dcrire facilement certains algorithmes : e e e la complexit dun algorithme rcursif est plus dicile ` calculer, e e a formules pour les algorithmes diviser pour rgner (cf. poly) e le compilation dune fonction rcursive est plus complique e e programme un peu plus lent dans certains cas.