Introduction à la complexité algorithmique
Pourquoi étudier la complexité ?
L'étude de la complexité algorithmique est fondamentale en informatique. Elle nous permet de comprendre et de prédire les performances d'un algorithme avant même de l'exécuter sur de grandes quantités de données.
Key Concepts:
- Efficacité des algorithmes : Il s'agit de la capacité d'un algorithme à résoudre un problème donné en utilisant le moins de ressources possible. Ces ressources sont principalement le temps de calcul et l'espace mémoire.
- Temps d'exécution : C'est le temps nécessaire à un algorithme pour s'achever. Il dépend de la taille des données d'entrée, de la puissance de la machine, et de l'algorithme lui-même.
- Utilisation des ressources : Outre le temps, un algorithme consomme de la mémoire vive (RAM) pour stocker les données et les variables. L'optimisation de cette utilisation est cruciale pour les grands ensembles de données.
- Comparaison d'algorithmes : Lorsque plusieurs algorithmes peuvent résoudre le même problème, l'analyse de leur complexité permet de choisir le plus performant. Un algorithme peut être plus rapide qu'un autre pour de petites entrées, mais devenir beaucoup plus lent pour de grandes entrées. Comprendre la complexité nous aide à faire le bon choix.
Étudier la complexité permet de choisir l'algorithme le plus adapté à la taille des données et aux contraintes de performance. Un algorithme "lent" pour des petites données peut être inutilisable pour des données massives.
Notions de base de la complexité
Pour analyser la complexité, nous avons besoin de quelques définitions :
Key Concepts:
- Opération élémentaire : C'est une opération de base dont le temps d'exécution est considéré comme constant et indépendant de la taille des données. Exemples : une addition, une affectation, une comparaison, l'accès à un élément d'un tableau.
- Taille des données (n) : C'est une mesure de la quantité d'informations que l'algorithme doit traiter. Pour un tri, c'est le nombre d'éléments dans la liste ; pour un graphe, c'est le nombre de sommets ou d'arêtes. La complexité est généralement exprimée en fonction de .
- Cas le meilleur (best case) : C'est la situation où l'algorithme s'exécute le plus rapidement possible. Par exemple, chercher un élément qui est le premier d'une liste.
- Cas le pire (worst case) : C'est la situation où l'algorithme s'exécute le plus lentement possible. Par exemple, chercher un élément qui n'est pas dans la liste (ou le dernier) lors d'une recherche linéaire. C'est souvent ce cas que l'on étudie pour garantir une performance minimale.
- Cas moyen (average case) : C'est la performance moyenne de l'algorithme sur un ensemble représentatif d'entrées. Il est souvent plus difficile à calculer car il nécessite des hypothèses sur la distribution des données d'entrée.
Notation asymptotique (Grand O)
La notation de Landau, ou notation Grand O (), est l'outil principal pour exprimer la complexité. Elle décrit la manière dont le temps d'exécution (ou l'espace mémoire) d'un algorithme croît à mesure que la taille des données d'entrée () augmente, en ignorant les facteurs constants et les termes d'ordre inférieur.
Key Concepts:
- Définition de O(n) : On dit qu'une fonction est en s'il existe des constantes positives et telles que pour tout , . En clair, est une borne supérieure du comportement de pour les grandes valeurs de .
- Ordres de grandeur courants : Ce sont les fonctions les plus fréquemment rencontrées :
- (constant) : Le temps d'exécution est indépendant de . Ex: Accéder à un élément dans un tableau par son index.
- (logarithmique) : Le temps d'exécution augmente très lentement avec . Typiquement, les algorithmes qui divisent le problème en deux à chaque étape. Ex: Recherche dichotomique.
- (linéaire) : Le temps d'exécution est directement proportionnel à . Ex: Parcourir une liste une seule fois.
- (quasi-linéaire) : Plus rapide que , typique des algorithmes de tri efficaces. Ex: Tri fusion, Tri rapide (en moyenne).
- (quadratique) : Le temps d'exécution est proportionnel au carré de . Souvent lié à des boucles imbriquées. Ex: Tri par sélection, Tri à bulles.
- (exponentiel) : Le temps d'exécution croît extrêmement rapidement. Ces algorithmes sont généralement inutilisables pour des même modérés. Ex: Problème du voyageur de commerce (brute force).
- (factoriel) : Croissance encore plus rapide que l'exponentielle. Très rares en pratique pour des problèmes d'optimisation.
Calcul de la complexité :
- Identifier l'opération élémentaire dominante.
- Compter combien de fois cette opération est exécutée en fonction de .
- Simplifier en utilisant la notation Grand O (ignorer les constantes et les termes d'ordre inférieur).
Exemple : Somme des éléments d'un tableau
def somme(tableau):
total = 0 # O(1)
for x in tableau: # Boucle exécutée n fois
total = total + x # O(1) à chaque itération
return total # O(1)
La boucle s'exécute fois, et à chaque fois, une opération élémentaire est effectuée. La complexité est donc .
Analyse de la complexité spatiale et temporelle
Nous distinguons deux types de complexité :
Key Concepts:
- Complexité en temps : Mesure le temps d'exécution de l'algorithme en fonction de la taille des données d'entrée . C'est ce que nous avons vu avec la notation Grand O.
- Complexité en espace : Mesure la quantité de mémoire (espace de stockage) utilisée par l'algorithme en fonction de la taille des données d'entrée . Cela inclut l'espace pour les variables, les structures de données supplémentaires, et la pile d'appels (pour les fonctions récursives).
- en espace signifie que l'algorithme utilise une quantité constante de mémoire, indépendamment de .
- en espace signifie que la mémoire utilisée est proportionnelle à .
Compromis temps/espace (trade-off) : Souvent, il existe un compromis entre la complexité en temps et la complexité en espace. Il est parfois possible de rendre un algorithme plus rapide en utilisant plus de mémoire, ou inversement. Le choix entre optimisation temps ou espace dépend des contraintes spécifiques du problème.
- Tri fusion : en temps, mais en espace car il nécessite un tableau temporaire de taille pour fusionner les sous-listes.
- Tri rapide : en temps (en moyenne), et en espace (en moyenne) pour la pile d'appels récursifs, mais peut atteindre dans le pire des cas.
- Recherche dichotomique : en temps et en espace (version itérative) ou en espace (version récursive pour la pile d'appels).


