Université de Provence Licence Math-Info Première Année V. Phan Luong Algorithmique et Programmation en Python Cours 5 : Notions d’Algorithme et de Complexité 1 Généralité Un algorithme est une méthode effective pour résoudre un problème. Par les termes “méthode effective” on s’entend que la méthode peut réellement résoudre le problème en un nombre fini d’opérations dont l’exécution peut se faire en un temps fini. La spécification d’un algorithme se faire d’abord par la spécification formelle du problème qu’il résoudre, suivie d’une méthode qu’il adopte pour résoudre le problème. Il faut bien analyser le problème pour le comprendre et déterminer les données essentielles du problème et ce que l’on cherche comme solution. Les données essentielles du problème constituent ce que l’on appelle l’entrée de l’algorithme. Ce que cherche le problème constitue la sortie de l’algorithme. Par exemple, le problème de calculer le pgcd de deux entiers naturels a pour l’entrée les deux entiers et la sortie le plus grand commun diviseur de ces deux entiers. La méthode de résolution est spécifiée en un nombre fini d’étapes dont chacune exécute une opération bien connue et finie pour assurer que chaque étape termine et que l’algorithme termine en un temps fini. Une bonne connaissance du domaine d’application est nécessaire pour pouvoir spécifier une méthode de résolution d’un problème à résoudre. Un algorithme est efficace si sa méthode de résolution optimise l’espace de mémoire et le temps de calcul pour résoudre le problème. L’efficacité est estimée par la notion de complexité. On a donc la complexité en espace de mémoire et la complexité en temps d’exécution. Ces complexités sont estimées en fonction de la grandeur des données d’entrée du problème. Dans l’ordre de plus efficace au moins efficace, on peut citer les algorithmes dont l’estimation est en fonction logarithme, linéaire, quadratique, exponentielle de la grandeur des données d’entrée. Pour estimer de manière indépendante de tous ordinateurs, l’efficacité en temps d’exécution est calculée en nombre d’opérations de base de l’agorithme. Par exemple, pour un 1 algorithme de calcul du pgcd, la complexité en temps peut être estimée en nombre d’opérations de division. Dans la suite on étudie certain nombre d’algorithmes concernant les listes. 2 Recherche dans une liste 2.1 Liste non triée Problème de recherche d’un élément dans une liste. Entrée : un élément x et une liste L. Sortie : un index de la liste si x existe dans L, -1 sinon. Méthode : Informelle : Parcourir la liste en comparant chaque élément de L avec x. On s’arrête lors de la première rencontre de x et retourne l’index de cette rencontre. Si la liste est toute parcourue et on ne voit pas x, alors retourne -1. Formelle : Pour chaque case de la liste, commencant par la première case, faire Comparer x avec l’élément de la case ; Si x est identique à l’élément, alors retourner l’index courant et s’arrête. Sinon, passer à la case suivante Si on arrive à ce stade, alors retourner -1 Fonction Python : def cherche(x, L) : i, n = 0, len(L) while i < n : if x == L[i] : return i i = i + 1 return -1 ## appels de fonction print cherche (5, [4,7,5,3,6,1]) 2 print cherche (5, [4,7,4,3,6,1]) La complexité en nombre de comparaisons de données (l’opération x == L[i]) : – Au pire, on doit comparer x avec tous les éléments de L. Donc, soit n le nombre d’élémements de L, alors la complexité en nombre de comparaisons de données est n ; elle est en fonction linéaire de la grandeur de la liste. – Au meilleur, on voit x à la première case de la liste. La complexité est en une comparaison. – La commplexité moyenne est n/2. 2.2 Liste triée On peut toujours appliquer l’algorithme ci-dessus pour rechercher un élément dans une liste triée. Cependant, pour cette configuration spéciale, on autre algorithme plus efficace qui effectue la recherche de manière dichotomique. Problème de recherche d’un élément dans une liste triée. Entrée : un élément x et une liste triée L. Sortie : un index de la liste si x existe dans L, -1 sinon. Méthode : Si la liste est vide, alors retourne -1. Sinon, Comparer x avec l’élément au milieu de la liste. Si x est égal à cet élément, alors retourner l’index du milieu. Sinon, si x est supérieur à cet élément, alors re-appliquer la recherche dans la partie droite de la liste. Sinon, re-appliquer la recherche dans la partie gauche de la liste. 3 Fonction Python : # fonction iterative def cherch_ord(x, L): g, d = 0, len(L) - 1 while g <= d : m = (d + g)/2 if x == L[m]: return m elif x > L[m]: g = m +1 else : d = m - 1 if g > d : return -1 #else: return -1 # fonction recursive def cherch_ord_r(x, L, g, d): if g <= d : m = (d + g)/2 if x == L[m]: return m elif x > L[m]: return cherch_ord_r(x, L, m+1, d) else : return cherch_ord_r(x, L, g, m-1) if g > d : return -1 #else: return -1 # Tests L = [4, 6, 7, 9, 12, 15, 35, 56] 4 x = input(’Entrer un nombre: ’) print "Test de la fonction itérative: " print cherch_ord(x, L) print "Test de la fonction récursive: " g, d = 0, len(L) - 1 print cherch_ord_r(x, L, g, d) La complexité en nombre de comparaisons de données (l’opération x == L[i]) : – Au meilleur, on voit x à la case au milieu de la liste. La complexité est en une comparaison. – Au pire, on doit comparer x avec l’élément au milieu des parties divisées successivement de L, jusqu’à ce que la dernière partie (de longueur 1). Pour atteindre cette longueur, le nombre de divisions successives est log2 (n), car 2log2 (n) = n. 3 Tris dans une liste 3.1 Un tri simple La méthode de tri (dans l’ordre croissant) la plus intuitive est de rechercher d’abord l’élément minimal de la liste, le placer au début de la liste et recommencer le tri dans la queue de la liste, ainsi de suite jusqu’à ce que la queue de la liste courant est vide. Entrée : une liste L. Sortie : la même liste mais triée dans l’ordre croissant. Méthode : trier par permutation. Parcourir L à partir du premier élément, pour chaque élément faire Pour la partie de la liste commençant par l’élément courant, chercher un élément minimal de cette partie ; permuter l’élément minimal avec l’élément au début de la partie. Retourner la liste. 5 Fonction Python : # Chercher l’index du premier élément minimal de la partie # de la liste commençant par index i def index_min(L, i): min = L[i] m, n, k = i, i+1, len(L) while n < k : if min > L[n]: min = L[n] m = n n = n+1 return m # Le tri par permutation def tri_permuta(L) : i, n = 0, len(L) while i < n-1 : k = index_min(L, i) tmp = L[i] L[i] = L[k] L[k] = tmp i = i+1 # Tests L = [6,12,7,5,9,4,8,3] tri_permuta(L) print L Complexité en temps : La recherche d’un élément minimal d’une partie de la liste coûte k-1 comparaisons, où k est le nombre d’élément de la partie. 6 Pour faire le trie, l’algorithme considére n-1 parties (sous-listes) de L de longueurs n-1, n-2, ..., 1, respectivement. Donc, le nombre total de comparaisons pour achever le tri est 1 + 2 + ... + n-2 + n-1 = n(n-1)/2. La complexité en temps de cet algorithme est en fonction quadratique de n (la taille de la liste). 3.2 Le tri rapide Un tri plus efficace est connu sous le nom le tri rapide (ou dichotomique). La méthode de ce tri consiste en étapes suivantes : 1) Prendre un élément, appelon le pivot, de la liste courante et le placer à une position dans la liste telle que tous les éléments à droite du pivot sont supérieurs au pivot et tous les éléments à droite du pivot sont inférieurs ou égaux au pivot. 2) Re-appliquer les actions du point 1) pour chaque partie gauche et droite de la liste, ainsi de suite jusqu’à ce que les parties se reduite en un seul élément. Souvent le premier élément de la partie courante est choisi comme le pivot de la partie. Fonction Python : # choisir le premier element de l comme pivot et le placer au bon endroit def placer(L, g, d) : v, m, i = L[g], g, g+1 while i <= d : if L[i] <= v : m = m+1 tmp = L[m] L[m] = L[i] L[i] = tmp i = i+1 tmp = L[g] L[g] = L[m] L[m] = tmp return m 7 # Le tri rapide def monqsort(L, i, j): if i < j : m = placer(L, i, j) monqsort(L, i, m-1) monqsort(L, m+1, j) L = [3,5,8,3,7,4,5,9] i, j = 0, len(L)-1 monqsort(L, i, j) print L Dans le pire des cas, la complexité du tri rapide en temps est une fonction quadratique de la taille de la liste. C’est le cas de trier une liste déjà bien ordonnée. En moyenne, on peut montrer que le nombre de comparaisons en nlog2 (n) où n est la taille de la liste. 8