IFT-2000: Structures de Données Introduction à lanalyse dalgorithmes Dominic Genest, 2009
Analyse dalgorithmes Le temps que prend un programme à sexécuter dépend de: – la façon dont il est implanté – lordinateur sur lequel il est exécuté – la quantité de mémoire disponible au moment où il est exécuté – les autres applications qui sont exécutées en même temps – etc. Pour comparer deux algorithmes qui effectuent une même tâche, chronométrer leur exécution donne une bien mauvaise comparaison. On aura donc recours à lanalyse dalgorithmes, basée sur la branche des mathématiques qui sappelle lanalyse fonctionnelle, pour effectuer ces comparaisons de façon beaucoup plus fiable. Dominic Genest, 2009
Analyse dalgorithmes On sintéresse dabord à la fonction qui donne le temps que met un algorithme à sexécuter, en fonction de la taille des données quon lui fournit. Par exemple, on pourrait chronométrer un algorithme et obtenir expérimentalement la fonction suivante: t(N) = 5*N millisecondes Ce qui compte, du point de vue de lanalyse dalgorithmes, cest uniquement la forme de cette fonction, laquelle est définie par lappartenance à un ensemble de fonctions qui est spécifié par une notation spéciale avec un grand O et des parenthèses. Par exemple, la fonction suivante appartient à lensemble de toutes les fonctions qui sont constituées dune constante quelconque multipliée par N, puis dautres termes optionnels moins significatifs. Cet ensemble est représenté par O(N). Autrement dit, la fonction « 5*N+2.3 » fait partie de lensemble O(N). On dira alors que le temps dexécution de notre algorithme est dans O(N). On peut aussi dire que ce temps est de lordre de N. Dominic Genest, 2009
Analyse dalgorithmes Dans certains cas, léquation qui définit le temps dexécution dun algorithme fait apparaître N². Dans ce cas, il est dans O(N²). Cest souvent (pas toujours!) le cas quand nous avons deux boucles imbriquées. Lalgorithme de Floyd-Warshall, par exemple, est dans O(N³). Il fait en effet intervenir trois boucles imbriquées dont le nombre ditération dépend linéairement de N, où N est le nombre de nœuds du graphe en question. Dominic Genest, 2009
Analyse dalgorithmes Dans dautres cas, le temps dexécution dun algorithme ne dépend même pas du nombre déléments. On dit donc que ce temps est constant, et quil est dans O(1). Dominic Genest, 2009
Analyse dalgorithmes Pour déterminer lappartenance à un ensemble de fonctions noté par O(…), il nest pas nécessaire de chronométrer un algorithme. On peut le faire, la plupart du temps, en procédant à une analyse théorique de lalgorithme. La théorie complète à cet effet est le sujet dun cours entier. Nous abordons, dans le cadre du présent cours, uniquement les cas les plus simples. Dominic Genest, 2009
Boucles void f(int N) { int i; for(i=0;i<N;i++) printf(Allo\n); } Cet algorithme-ci prendra un certain temps constant, disons x secondes, pour initialiser la variable i, puis N fois un autre temps, disons y secondes, pour faire lappel à « printf ». Nous aurons donc un temps égal à: t(N)=x+N*y Pour déterminer lappartenance de cette fonction à un ensemble O(quelque chose), il faut dabord trouver le terme dominant de léquation. Cette notion est définissable formellement par du calcul de limite: il est en fait question de la valeur quand N tend vers linfini. Pour notre cours, nous nous contenterons de le définir comme le terme avec lexposant le plus grand appliqué à N. Pour établir lensemble O(quelque chose), il faut donc éliminer tous les autres termes de léquation, puis éliminer toutes les constantes multiplicatives. On arrive donc à: t(N) Є O(N) Dominic Genest, 2009
Boucles void f(int N) { int i,j; for(i=0;i<N;i++) for(j=0;j<N;j++) printf(%d-%d\n,i,j); } Cet algorithme-ci est dans O(N²) Dominic Genest, 2009
Boucles void f(int N) { int i,j; for(i=0;i<N;i++) for(j=0;j<N;j++) printf(%d-%d\n,i,j); for(i=0;i<N;i++) printf(%d\n,i); } Celui-ci aura un temps dexécution de la forme suivante: t(N)=x+N²*y+N*z Le terme dominant est celui avec N². Pour déterminer lappartenance de t(N) à un O(…), il faut éliminer les autres termes, soit x et N*z. Il faut aussi éliminer la constante multiplicative y. On obtient donc: t(N) Є O(N²) Dominic Genest, 2009
Boucles void f(int N) { int i; for(i=0;i<10000;i++) printf(Allo\n); } Cet algorithme-ci prends un temps de la forme suivante: t(N) = 10000*x Puisque son temps dexécution ne dépend pas de N, même si la boucle est très longue: T(N) Є O(1) Dominic Genest, 2009
Boucles void f(int N) { int i; for(i=0;i<5340*N;i++) printf(Allo\n); } Cet algorithme-ci prends un temps de la forme suivante: t(N) = x+5340*N*y Puisque quil faut faire abstraction de toute constante multiplicative: T(N) Є O(N) Dominic Genest, 2009
Boucles void f(int N) { int i; for(i=0;i<N;i++) for(j=0;j<i;j++) printf(Allo\n); } On a donc: t(N) Є O(N²) Dominic Genest, 2009
Ordres non polynômiaux Il ny a pas que des O(N p ). Il y a aussi des ordres de forme bien différente: – O(log N) – O(2 N ) – O(N!) – O(N log N) – O(N² log N) – O(log log N) – O(N N ) – etc. Dominic Genest, 2009
Comparaisons selon lordre De façon générale, on peut affirmer quun algorithme dont le temps est dans O(N) est plus rapide quun algorithme dont le temps est dans O(N²). Par contre, cela nest vrai, en théorie, que lorsque N tend vers linfini. Notre ignorance volontaire des constantes multiplicatives et des termes qui ne sont pas dominants fait en sorte que nous ne connaissons pas réellement la comparaison des temps dexécution pour des valeurs finies de N. En réalité, il existe un certain seuil de quantité de données, déterminable expérimentalement, à partir duquel nous sommes assurés que lalgorithme dans O(N) est plus rapide que celui dans O(N²). Par exemple: lalgorithme de tri rapide (quick-sort), que nous étudierons à la fin du cours, est dans O(N log N). Lalgorithme de tri insertion, quant à lui, est dans O(N²). En théorie, un algorithme de lordre de N² est plus lent quun algorithme de lordre de N log N. Par contre, la plupart du temps, il est établi que le tri insertion est plus rapide que le tri rapide (quick-sort) quand on a moins de 500 éléments à trier. Dominic Genest, 2009
Comparaisons dalgorithmes t(N) (secondes) N O(N) O(N²) O(N log N) Seuils à déterminer expérimentalement Dominic Genest, 2009
Les ordres rencontrés au cours De quel ordre sont les algorithmes suivants? – Insertion à la fin dans un tableau dynamique. – Insertion au début dans un tableau statique dont le début nest pas variable. – Insertion au milieu dun tableau dynamique – Accès au nième élément dun tableau dynamique – Accès au nième élément dune liste chaînée – Insertion dans le milieu dune liste chaînée, une fois la position trouvée. – Algorithme de Floyd – Algorithme de Dijkstra – Algorithme de parcours de graphe par largeur ou profondeur. – Recherche dun élément dans une liste chaînée ou dans un tableau dynamique. Dominic Genest, 2009
Importance du choix dun algorithme selon son analyse Peut-on affirmer que les constantes multiplicatives cachées font en sorte que ce choix soit peu important finalement? Peut-on affirmer que de toutes façons, vue lévolution rapide des performances des ordinateurs, ce choix aura très peu dimpact sur la performance des logiciels? Réponse: NON. Il sagit dune erreur classique de programmeur débutant (surtout pour la deuxième question!). Le choix dun algorithme (et donc de la structure de données qui permet de lemployer) peut souvent, en pratique, faire la différence entre 3 microsecondes ou 3 20 années. À titre dexemple, supposons que pour effectuer un tri, on a le choix entre un algorithme dans O(N log N) et un autre dans O(N²). À toutes fins pratiques, en ce qui concerne les implémentations courantes de tris, les constantes multiplicatives ne sont pas si déterminantes que ça. Si on a déléments à trier, lalgorithme dans O(N²) sera environ fois plus lent! Le choix du bon algorithme et de la bonne structure de données est donc bien plus important que nimporte quelle optimisation raffinée et, surtout, beaucoup plus important que lachat dun ordinateur plus performant! Dominic Genest, 2009