Analyse de la complexité des algorithmes

Représentation d'une recherche linéaire (en violet) face à une recherche binaire (en vert). La complexité algorithmique de la seconde est logarithmique alors que celle de la première est linéaire.

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.

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.

Exemple de la 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 :

  1. Recherche linéaire : parcourir les pages dans l'ordre (alphabétique) jusqu'à trouver le nom cherché.
  2. 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 , ça veut dire 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.

Ordre de grandeur du temps nécessaire à l'exécution d'un algorithme d'un type de complexité
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é.

  1. 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.

Bibliographie

[modifier | modifier le code]

Articles connexes

[modifier | modifier le code]