Analyse de la complexité des algorithmes — Wikipédia

L'analyse de la complexité d'un algorithme consiste en l'étude formelle de la quantité de ressources (par exemple de temps ou d'espace) nécessaire à l'exécution de cet algorithme. Celle-ci ne doit pas être confondue avec la théorie de la complexité, qui elle étudie la difficulté intrinsèque des problèmes, et ne se focalise pas sur un algorithme en particulier.
Histoire
[modifier | modifier le code]Quand les scientifiques ont voulu énoncer formellement et rigoureusement ce qu'est l'efficacité d'un algorithme ou au contraire sa complexité, ils se sont rendu compte que la comparaison des algorithmes entre eux était nécessaire et que les outils pour le faire à l'époque[1] étaient primitifs. Dans la préhistoire de l'informatique (les années 1950), la mesure publiée, si elle existait, était souvent dépendante du processeur utilisé, des temps d'accès à la mémoire vive et de masse, du langage de programmation et du compilateur utilisé.
Une approche indépendante des facteurs matériels était donc nécessaire pour évaluer l'efficacité des algorithmes. Donald Knuth fut un des premiers à l'appliquer systématiquement dès les premiers volumes de sa série The Art of Computer Programming. Il complétait cette analyse de considérations propres à la théorie de l'information : celle-ci par exemple, combinée à la formule de Stirling, montre que, dans le pire des cas, il n'est pas possible d'effectuer, sur un ordinateur classique, un tri général (c'est-à-dire uniquement par comparaisons) de N éléments en un temps croissant avec N moins rapidement que N ln N.
Différentes approches
[modifier | modifier le code]L'approche la plus classique est donc de calculer le temps de calcul dans le pire des cas.
Il existe au moins trois alternatives à l'analyse de la complexité dans le pire des cas. La complexité en moyenne des algorithmes, à partir d'une répartition probabiliste des tailles de données, tente d'évaluer le temps moyen que l'on peut attendre de l'évaluation d'un algorithme sur une donnée d'une certaine taille. La complexité amortie des structures de données consiste à déterminer le coût de suites d'opérations. L'analyse lisse d'algorithme, plus récente, se veut plus proche des situations réelles en calculant la complexité dans le pire des cas sur des instances légèrement bruitées.
Algorithmes galactiques
[modifier | modifier le code]Parfois, l'analyse de la complexité d'un algorithme est trompeur. En effet, on peut trouver un algorithme disposant d'une meilleure complexité asymptotique mais n'étant pas utilisable en pratique car une constante cachée est si grande que l'algorithme ne sera jamais utilisé pour un jeu de donnée trouvé sur Terre.
Méthodologie
[modifier | modifier le code]Afin d'analyser une complexité d'un algorithme s'exécutant sur un ensemble d'instances on procède en plusieurs étapes :
La première consiste à déterminer une fonction de coût , représentant au choix soit le coût en temps, par exemple nombre d'étapes nécessaires, soit le coût en espace, par exemple le nombre d'octets occupés simultanément, de l'exécution de l'algorithme sur l'entrée [2].
Ensuite, comme l'on veut déterminer le comportement pris par l'algorithme en fonction d'une taille des instances , il faut alors déterminer cette taille des instances . Cela peut par exemple être le nombre de caractères d'une chaîne de caractère.
Après, l'on doit choisir choisir une mesure de coût , afin d'évaluer le coût de l'algorithme en fonction de la taille des instances et non plus des instances elles même. La mesure la plus utilisée est le coût dans le cas le plus défavorable mais l'on peut aussi choisir le coût dans le cas le plus favorable ou encore le coût moyen[2].
Enfin, pour comparer le comportement asymptotique de notre mesure de coût et à d'autres mesures, on raisonne alors sur des ordres de grandeurs de croissance[3]. On peut vouloir montrer une majoration , une minoration ou les deux bornes .
Calcul du coût d'un algorithme en fonction de l'instance
[modifier | modifier le code]Complexité temporelle
[modifier | modifier le code]Analyser la complexité temporelle d'un algorithme, c'est compter le nombre d'opérations élémentaires réalisées par cet algorithme[2].
Opérations élémentaires
[modifier | modifier le code]Les opérations élémentaires comme les opérations arithmétiques, les opérations booléennes, les affectations, les conditions de branchement n'ont pas toutes le même coût. Il est alors aussi difficile qu'inutile de chercher à compter de manière exhaustive toutes les opérations élémentaires réalisées par un algorithme sur une instance. Afin d'obtenir un ordre de grandeur intéressant selon le contexte, on sélectionnera les opérations que l'on veut compter[2].
Pour toute la famille des tri par comparaison, c'est uniquement la métrique du nombre de comparaisons que l'on compte. Pour autant, pour des tris externes, la métrique du nombre d'accès tableau est couramment utilisée[2].
Analyse amortie
[modifier | modifier le code]Lorsque l'on calcule le nombre d'opérations élémentaires, le coût, effectuées par une suite d'opérations sur une structure de donnée. Ce coût est donc la somme des coût des opérations individuelles[3].
Si l'on dispose d'une estimation du coût dans le cas le plus défavorable, des opérations individuelles, on obtient une majoration du coût total en sommant ces majorations[3].
Or, très souvent, cette majoration est très grossière, car le cas le plus défavorable « ne se répète pas », il y a une « compensation » de coût au cours de la suite d'opérations[3].
Une analyse plus précise que la majoration grossière est appelée analyse amortie.
Complexité en espace
[modifier | modifier le code]La complexité spatiale d'un algorithme représente la quantité de mémoire utilisée par celui-ci, cette quantité est souvent mesurée en nombre d'octet[2].
Le coût spatial d'une exécution d'un algorithme est la quantité maximale de mémoire utilisée simultanément par cet algorithme au cours de l'exécution[2].
Détermination de la taille des instance
[modifier | modifier le code]Déterminer la taille d'une entrée c'est compter le nombre de bits ou d'octet occupés par l'entrée.
Pour un graphe , on prendra souvent comme taille du graphe.
Pour n tableau de chacun de taille bits, si la taille réelle est . Pourtant, bien souvent on approximera cette taille à car le terme n'apparaît pas dans l'expression du coût. Cela n'est pas possible pour le tri comptage.
Exemple : le tri par comptage
[modifier | modifier le code]Dans l'analyse la complexité du tri comptage, sans hypothèses particulières, on peut facilement calculer par mégarde une complexité erronée. En considérant en entrée le tableau de éléments de bits. Si l'on considère que la taille de l'entrée est , on peut alors montrer que l'algorithme est en . Or ce raisonnement est erroné, car le nombre réel de bits occupés par l'entrée est la complexité de l'algorithme étant alors , exponentielle en la taille de l'entrée. Ce résultat est cohérent, car l'hypothèse particulière du tri comptage, nécessaire à l'établissement de la complexité linéaire, est une majoration du nombre de bits, on retombe alors dans le premier cas[4].
Mesures du coût d'un algorithme en fonction le la taille des instances
[modifier | modifier le code]Soit un problème et un algorithme qui le résout. Sur une instance de taille , l'algorithme s'exécute sur en un certain nombre d'opérations élémentaires, on appelle ce nombre le cout [3].
Si le coût d'un algorithme dépend de l'instance d'entrée, elle n'est pas fixée pour une taille donnée. Pour autant, l'objectif est d'évaluer le coût d'un algorithme en fonction de la taille de l'instance. On définit alors trois nuances de fonctions de couts dépendant de ce critère, qui sont des points de repère pour appréhender la complexité réelle[2].
Coût dans le pire cas
[modifier | modifier le code]Le coût dans le cas le plus défavorable d'un algorithme pour les instances de taille , noté , est le maximum de ses couts sur toutes les instances de taille [3].
Autrement dit, [5]
Coût dans le meilleur cas
[modifier | modifier le code]Le coût dans le cas le plus favorable d'un algorithme pour les instances de taille , noté , est le minimum de ses coûts sur toutes les instances de taille [3].
Autrement dit, [5]
Coût moyen
[modifier | modifier le code]Lorsque le cas le plus défavorable est un cas pathologique, on peut vouloir calculer un coût moyen.
Définition générale
[modifier | modifier le code]Le coût moyen d'un algorithme pour les instances de taille , noté , avec est la probabilité d'apparition de l'instance parmi les instances de sa taille, est donc l'espérance des coûts des instances de taille [3].
Autrement dit, [3].
Cas particulier de la distribution uniforme
[modifier | modifier le code]Le plus souvent, on suppose que toutes les instances d'une taille donnée ont la même probabilité d'apparition.
Le coût moyen uniforme d'un algorithme pour les instances de taille , noté , en notant le nombre d'instances de taille , utilise la distribution de probabilité uniforme [3].
Autrement dit, [3].
Autres fonctions de coût
[modifier | modifier le code]D'autres fonctions de coût existent comme la complexité générique des algorithmes ou encore l'analyse lisse d'algorithme mais elle sont moins courantes.
Comparaison asymptotique en informatique
[modifier | modifier le code]Dans l'étude de la complexité d'un algorithme, on s'intéresse à la manière dont la mesure de coût évolue pour des tailles d'instances très grandes. Cette étude du comportement asymptotique de la mesure de coût, est appelée la complexité asymptotique. L'objectif n'étant pas d'obtenir une valeur exacte de cette complexité mais plutôt un ordre de grandeur, on va alors regrouper les différentes fonctions de coûts dans des ensembles exprimant un comportement asymptotique, les notations de Landau[2].
Les notations de Landau sont un moyen d'exprimer l'ordre de grandeur du nombre d'opérations effectuées ou nécessaires à effectuer par un algorithme pour résoudre un problème. Trois situations sont décrites par ces notations. La plus fréquente, la notation donne une majoration de l'ordre de grandeur. La notation est une minoration de l'ordre de grandeur, et la notation dénote une équivalence sur les ordres de grandeur[3].
Dans le cadre de la complexité en informatique, on se limite à définir ces notations au voisinage de et pour des fonctions à valeurs positives.
Majoration
[modifier | modifier le code]La notation , « grand O », est couramment utilisée pour montrer qu'un algorithme de fonction de cout ne s'exécute pas, en ordre de grandeur, en plus d'opérations que .
Informellement, on dit que est en lorsque l'on peut majorer par à une constante près[6].
Par exemple, le tri par insertion est en .
Définition formelle
[modifier | modifier le code]Soit , on désigne par au voisinage de l'ensemble des fonctions de cout positives tels qu'il existe tels que pour tout [3],[2].
Autrement dit, .
Exemple
[modifier | modifier le code]Conventions de notation
[modifier | modifier le code]La principale difficulté avec ce concept provient de la convention de notation abusive historique « de Landau » qui veut que l'on écrive ou encore au lieu de [3].
De la même manière, la convention veut historiquement que l'on écrive au lieu de [3].
Pour autant, n'implique pas nécessairement que . De même, n'implique pas forcément . Dans les deux cas, l'axiome d'égalité est contredit[3].
Minoration
[modifier | modifier le code]La notation caractérise souvent un problème. Elle sert à justifier que tout algorithme résolvant le problème, de fonction de cout , ne s'exécute pas, en ordre de grandeur, en plus d'opérations que .
Informellement, on dit que est en lorsque l'on peut minorer par à une constante près[6].
Par exemple, le nombre minimum de comparaisons d'un tri par comparaison est en [3].
Définition formelle
[modifier | modifier le code]Soit , on désigne par au voisinage de l'ensemble des fonctions de cout positives tels qu'il existe tels que pour tout [3],[2].
Autrement dit, .
Caractérisation : Une fonction appartient à si et seulement si appartient à [6].
Conventions de notation
[modifier | modifier le code]De la même manière que pour , la convention de notation abusive historique « de Landau » qui veut que l'on écrive ou encore au lieu de [3].
De la même manière, la convention veut historiquement que l'on écrive au lieu de .
Deux bornes
[modifier | modifier le code]La notation est une relation d'équivalence. On désigne par l'ensemble des fonctions qui « croissent » de façon comparable à [6].
Définition formelle
[modifier | modifier le code]Soit , on désigne par l'ensemble des fonctions pour lesquelles il existe des nombres tels que [3],[2].
Autrement dit, .
Conventions de notation
[modifier | modifier le code]De la même manière que pour et , la notation « de Landau » qui veut que l'on écrive ou encore au lieu de [3].
Théorème Général (Master Theorem)
[modifier | modifier le code]Pour les algorithmes de type « diviser pour régner », parfois la mesure du coût dépend de celle d' sous-problèmes de taille , il existe un critère général donnant l'ordre de grandeur de telles mesures de coût, il est appelé théorème général ou traditionnellement master theorem[2].
Ce critère s'applique aux équations de la forme [2]
On appelle exposant critique la valeur .
- Si et alors
- Si et alors
- Si et alors [2].
Exemple de l recherche dans une liste triée
[modifier | modifier le code]Supposons que le problème posé soit de trouver un nom dans un annuaire téléphonique qui consiste en une liste triée alphabétiquement. On peut s'y prendre de plusieurs façons différentes. En voici deux :
- Recherche linéaire : parcourir les pages dans l'ordre (alphabétique) jusqu'à trouver le nom cherché.
- Recherche dichotomique : ouvrir l'annuaire au milieu, si le nom qui s'y trouve est plus loin alphabétiquement que le nom cherché, regarder avant, sinon, regarder après. Refaire l'opération qui consiste à couper les demi-annuaires (puis les quarts d'annuaires, puis les huitièmes d'annuaires, etc.) jusqu'à trouver le nom cherché.
Pour chacune de ces méthodes il existe un pire des cas et un meilleur des cas. Prenons la méthode 1 :
- Le meilleur des cas est celui où le nom est le premier dans l'annuaire, le nom est alors trouvé instantanément.
- Le pire des cas est celui où le nom est le dernier dans l'annuaire, le nom est alors trouvé après avoir parcouru tous les noms.
Si l'annuaire contient 30 000 noms, le pire cas demandera 30 000 étapes. La complexité dans le pire des cas de cette première méthode pour entrées dans l'annuaire fourni est , cela signifie que dans le pire des cas, le temps de calcul est de l'ordre de grandeur de : il faut parcourir tous les noms une fois.
Le second algorithme demandera dans le pire des cas de séparer en deux l'annuaire, puis de séparer à nouveau cette sous-partie en deux, ainsi de suite jusqu'à n'avoir qu'un seul nom. Le nombre d'étapes nécessaire sera le nombre entier qui est immédiatement plus grand que qui, quand est 30 000, est 15 (car vaut 32 768). La complexité (le nombre d'opérations) de ce second algorithme dans le pire des cas est alors , ce qui veut dire que l'ordre de grandeur du nombre d'opérations de ce pire cas est le logarithme en base de la taille de l'annuaire, c'est-à-dire que pour un annuaire dont la taille est comprise entre et , il sera de l'ordre de . On peut écrire aussi bien ou , car et ont le même ordre de grandeur.
Complexité, comparatif
[modifier | modifier le code]Pour donner un ordre d'idée sur les différentes complexités, le tableau ci-dessous présente les différentes classes de complexité, leur nom, des temps d'exécution de référence et un problème de ladite complexité. Les temps d'exécution sont estimés sur la base d'un accès mémoire de 10 nanosecondes par étape. Les temps présentés ici n'ont aucune valeur réaliste, car lors d'une exécution sur machine de nombreux mécanismes entrent en jeu. Les temps sont donnés à titre indicatif pour fournir un ordre de grandeur sur le temps nécessaire à l'exécution de tel ou tel algorithme.
Temps | Type de complexité | Temps pour n = 1 | Temps pour n = 5 | Temps pour n = 10 | Temps pour n = 20 | Temps pour n = 50 | Temps pour n = 250 | Temps pour n = 1 000 | Temps pour n = 10 000 | Temps pour n = 1 000 000 | Problème exemple |
---|---|---|---|---|---|---|---|---|---|---|---|
complexité constante | 10 ns | 10 ns | 10 ns | 10 ns | 10 ns | 10 ns | 10 ns | 10 ns | 10 ns | accès à une cellule de tableau | |
complexité logarithmique | 10 ns | 10 ns | 10 ns | 10 ns | 20 ns | 30 ns | 30 ns | 40 ns | 60 ns | recherche dichotomique | |
complexité racinaire | 10 ns | 22 ns | 32 ns | 45 ns | 71 ns | 158 ns | 316 ns | 1 µs | 10 µs | test de primalité naïf | |
complexité linéaire | 10 ns | 50 ns | 100 ns | 200 ns | 500 ns | 2.5 µs | 10 µs | 100 µs | 10 ms | parcours de liste | |
complexité quasi linéaire | 10 ns | 50 ns | 100 ns | 200 ns | 501 ns | 2.5 µs | 10 µs | 100,5 µs | 10,05 ms | triangulation de Delaunay | |
complexité linéarithmique | 10 ns | 40 ns | 100 ns | 260 ns | 850 ns | 6 µs | 30 µs | 400 µs | 60 ms | tris par comparaisons optimaux (comme le tri fusion ou le tri par tas) | |
complexité quadratique (polynomiale) | 100ns | 250 ns | 1 µs | 4 µs | 25 µs | 625 µs | 10 ms | 1 s | 2.8 heures | parcours de tableaux 2D | |
complexité cubique (polynomiale) | 1 µs | 1.25 µs | 10 µs | 80 µs | 1.25 ms | 156 ms | 10 s | 2.7 heures | 316 ans | multiplication matricielle naïve | |
complexité sous-exponentielle | 10ns | 30 ns | 100 ns | 492 ns | 7 µs | 5 ms | 10 s | 3.2 ans | ans | factorisation d’entiers avec GNFS (le meilleur algorithme connu en 2018) | |
complexité exponentielle | 20ns | 320 ns | 10 µs | 10 ms | 130 jours | ans | ... | ... | ... | problème du sac à dos par force brute | |
complexité factorielle | 10ns | 1.2 µs | 36 ms | 771 ans | ans | ... | ... | ... | ... | problème du voyageur de commerce avec une approche naïve | |
complexité doublement exponentielle | 400 ns | 4.3 s | ans | ... | ... | ... | ... | ... | ... | décision de l'arithmétique de Presburger |
est le logarithme itéré.
Notes
[modifier | modifier le code]- ↑ D'après Donald Knuth The Dangers of Computer Science Theory, in Selected Papers on Analysis of Algorithms (CSLI Lecture Notes, no. 102.) (ISBN 1-57586-212-3), le tout premier travail de ce qui est maintenant appelé la théorie de la complexité computationnelle est la thèse de Demuth en 1956 : H. B. Demuth, Electronic Data Sorting --PhD thesis, Stanford University (1956)--, 92 pages, Partiellement reproduit in IEEE Transactions on Computer (1985), pp. 296-310.
- Informatique: MP2I, MPI cours et exercices corrigés, Ellipses, (ISBN 978-2-340-07034-9)
- D. Beauquier, J. Berstel, Ph. Chrétienne, Éléments d’algorithmique, (lire en ligne)
- ↑ Introduction to algorithms, MIT Press, (ISBN 978-0-262-03384-8 et 978-0-262-53305-8, OCLC 311310321, lire en ligne)
- Benjamin Wack, « Algorithmique et Analyse d’Algorithmes »
- François Schwarzentruber, « Complexit´ », sur https://people.irisa.fr/Francois.Schwarzentruber/algo1/01fiche_complexite.pdf,
Bibliographie
[modifier | modifier le code]- Olivier Carton, Langages formels, calculabilité et complexité, [détail de l’édition] (lire en ligne)
- (en) Sanjeev Arora et Boaz Barak, Computational Complexity : A Modern Approach, Cambridge University Press, (ISBN 0-521-42426-7)
- (en) Christos Papadimitriou, Computational Complexity, Addison-Wesley, (ISBN 978-0-201-53082-7)
- (en) Michael R. Garey et David S. Johnson, Computers and Intractability : A guide to the theory of NP-completeness, W.H. Freeman & Company, 1979 (ISBN 0-7167-1045-5)
- Richard Lassaigne et Michel de Rougemont, Logique et Complexité, Hermes, 1996 (ISBN 2-86601-496-0)
- Nicolas Hermann et Pierre Lescanne, Est-ce que P = NP ? Les Dossiers de La Recherche, 20:64–68, août-
Voir aussi
[modifier | modifier le code]Articles connexes
[modifier | modifier le code]- Théorie de la complexité (informatique théorique)
- Complexité, article général sur la complexité
- Complexité de Kolmogorov
- Explosion combinatoire
- Machine de Turing non déterministe
- Réduction polynomiale