Algorithmie PC 1 : Complexité corrigé 1 Élements de complexité 1.1 Définitions Le nombre d’opérations effectués ou la place mémoire prise par un programme est souvent notée en O(). Une définition mathématique de cette fonction peut être : Une fonction g(N) est en O(f (N )) (de l’ordre de f(N)) s’il existe deux constantes c0 et N0 tels que g(N ) < c0 f (N ), pour tout N > N0 . 1.2 Jeux des différences 1.2.1 Quelle est la différence entre O(1) et O(2) ? en notant f(N)=1 et g(N)=2 on a que pour tout N, f(N) < g(N) et g(N) < 3f(N). Ces deux fonctions sont donc équivalentes. On peut donner les règles suivantes : • O(f (N ) + g(N )) = O(f (N )) + O(g(N )) • O(A ∗ f (N )) = A ∗ O(f (N )) = O(f (N )) • f (N ) ∗ O(g(N )) = O(f (N ) ∗ g(N )) 1.2.2 Quelle est la différence entre O(N 2 + N 3 ) et O(N 3 ) ? De même que précédemment ces deux fonctions sont équivalentes. On a O(N 2 + N 3 ) = O(N 2 ) + O(N 3 ). Comme N 2 est en O(N 3 ), O(N 2 ) + O(N 3 ) = O(N 3 ) + O(N 3 ) = 2O(N 3 ) = O(N 3 ) 1 1.3 Calcul de complexité pour des algorithmes fictifs 1.3.1 Donnez une complexité de l’algorithme suivant (et dites ce qu’il fait) En dehors des boucles, il n’y a qu’une unique affectation. Il y a deux boucles imAlgorithme 1 Algorithme ne servant pas à grand chose Données n Début total=0 de i=1 a n-1 faire de j=i+1 a n faire total=total+1 Rendre k Fin briqués, et à l’intérieur des deux boucles une affectation, donc en O(1). Chaque boucle étant en O(n), la complexité de cet algorithme est en O(n2 ). On peut bien sur regarder toutes les opérations une à une et on trouverait un nombre d’opération égal à 1 + n(n-1)/2. L’algorithme fait de nombreuses choses : il compte les boucles, calcule le nombre de sous-ensembles à 2 éléments d’un ensemble à n éléments, etc... 1.3.2 Quel est la complexité d’un algorithme qui, à partir de n données initiales, examine toutes les données, en supprime une et recommence (examen des données et suppression) jusqu’à ce qu’il n’y ai plus de données. On peut utiliser la formule de récurrence C(N ) = N + C(N − 1), où C(N) est la complexité de l’algorithme à N données. On a alors C(N ) = N + N − 1 + N − 2 + ... + 1 = N (N + 1)/2 2 2.1 Récursion Définition La récursion est un principe fondamental en mathématique (la preuve par récurrence par exemple) et en informatique. Une définition simple de ce principe en informatique est qu’un programme récursif s’appelle lui-même. Pour éviter que le programme ne s’appelle indéfiniment, il est nécessaire d’avoir une condition d’arrêt, qui, lorsqu’elle est satisfaite empêche le programme de s’appeler lui-même. 2 2.2 Factorielle La définition mathématique de la fonction factorielle est pour tout entier N : ˙ − 1)! pour N ≥ 1 N ! = N (N 0! = 1 Voici une version algorithmique de l’équation : Algorithme 2 Algorithme Factorielle(N) Données N Début Si N = 0 Alors Rendre 1 Sinon Rendre N*Factorielle(N-1) Fin 2.2.1 L’algorithme 2 calcule-t-il bien une factorielle ? Ben oui, il calcule bien ce qu’il faut. On le prouve par récurrence. Pour n = 0 on a bien que Factorielle(0) = 1 = 1!. On suppose donc que pour n ≥ 0, Factorielle(n) = n!. Pour n + 1. L’algorithme retourne (n + 1) ∗ F actorielle(n). Par hypothèse de récurrence Factorielle(n) = n!, donc Factorielle(n+1) =(n+1)*Factorielle(n) = (n+1)*n! = (n+1)!. 2.2.2 Quel est le domaine de définition l’entier N de l’algorithme 2 ? Que se passe-t-il si N est en dehors de son domaine ? Le domaine de définition est l’ensemble des entiers naturels. Si N est un réel ou un entier négatif, l’algorithme ne s’arrête pas. Pour pallier ce problème on peut changer le test si N=0 alors rendre 1 par si N ≤ 0 alors rendre 1 2.2.3 Quelle est la complexité de l’algorithme 2 ? Encore une fois, on va calculer sa complexité par récurrence. Soit C(N) le nombre d’opération effectué par l’algorithme 2. On a C(0) = 1, puisqu’il y a une affectation (on ne compte pas le test comme une opération ici). De plus, C(N) = 1 + C(N-1) pour N > 0 puisque l’on multiplie le résultat de Factorielle(N-1) à N. 3 Ainsi, C(N) = 1 + C(N-1) = 1 + 1 + C(N-2) = .... = N + C(0) = N+1. Le calcul de Factorielle avec l’algorithme 2 est donc en O(n). 2.3 Fibonacci La définition mathématique de la suite de Fibonacci (destinée initialement à compter le nombre de lapin génération après génération. Le problème initial, proposé en 1202 était en effet : Partant d’un couple, combien de couples de lapins obtiendrons-nous après un nombre donné de mois sachant que chaque couple produit chaque mois un nouveau couple, lequel ne devient productif qu’après deux mois.) est pour tout entier N : F (N ) = F (N − 1) + F (N − 2) pour N ≥ 2 F (0) = F (1) = 1 2.3.1 S’inspirer de l’algorithme 2 pour écrire un programme récursif calculant la suite de Fibonacci. Algorithme 3 Algorithme Fibonacci(N) Données N Début Si N ≤ 1 Alors Rendre 1 Sinon Rendre Fibonacci(N-1) + Fibonacci(N-2) Fin 2.3.2 Calculer le nombre d’appels au programme Fibonacci lors de l’exécution de Fibonacci(n). Lorsque N vaut 0 ou 1, le nombre d’appel est égal à 1. Pour N≥ 2, le nombre d’appel à Fibonacci est égal au nombre d’appels de Fibonacci(N-1) plus le nombre d’appels de Fibonacci(N-2) plus 1. Le nombre d’appel à Fibonacci pour l’algorithme 3 est donc égal à 2*Fibonacci(N)-1, et est donc en O(Fibonacci(N)). Pour calculer le nombre d’appels précisément, on va trouver un équivalent à Fibonacci(N). La suite Fibonacci(N)/Fibonacci(N-1), si elle converge, converge vers Φ = √ 1+ 5 qui est la solution positive de l’équation x = 1 + 1/x. En effet, on a 2 Fibonacci(N)/Fibonacci(N-1) = 1 + Fibonacci(N-2)/Fibonacci(N-1) et donc si 4 la limite L existe elle satisfait L = 1 +1/L. Cette équation nous montre de plus que pour tout N, Fibonacci(N)/Fibonacci(N-1) >1. Il nous reste à montrer que Fibonacci(N)/Fibonacci(N-1) converge. En posant f (x) = 1+1/x, on a que Fibonacci(N)/Fibonacci(N-1) -Φ = f(Fibonacci(N1)/Fibonacci(N-2)) -f(Φ). La formule des accroissements fini nous indique donc qu’il existe c dans [min{Fibonacci(N-1)/Fibonacci(N-2), Φ}, max{Fibonacci(N1)/Fibonacci(N-2), Φ}] tel que : f(Fibonacci(N-1)/Fibonacci(N-2)) -f(Φ) = f’(c)((Fibonacci(N-1)/Fibonacci(N2)-Φ). Comme f 0 (x) = −1/x2 et que c > 1, on a que 0 < |f 0 (c)| < 1. Ainsi : |Fibonacci(N)/Fibonacci(N-1) − Φ| ≤ A|(Fibonacci(N-1)/Fibonacci(N-2) − Φ)|, avec A < 1. De là, |Fibonacci(N)/Fibonacci(N-1) − Φ| ≤ AN −2 |1 − Φ| et donc Fibonacci(N)/Fibonacci(N-1) converge bien vers Φ. Cette convergence nous permet ensuite de conclure que Fibonacci(N) est équivalent à ΦN . Le nombre d’appel à Fibonacci est donc en O(Φn ). 2.4 Conclusion ? Un nombre d’appel exponentiel, c’est trop ! L’algorithme proposé n’est pas un bon algorithme pour calculer la suite de Fibonacci. La raison en est que beaucoup beaucoup de choses sont calculées plusieurs fois. Par exemple Fibonacci(N-2) est calculée pour Fibonacci(N) et pour Fibonacci(N-1). Il faut trouver un moyen de ne calculer les choses qu’une et une seule fois. 2.5 Proposez un algorithme itératif calculant la suite de Fibonacci, et donner en sa complexité. La complexité est en O(N ). Ce qui n’a rien à voire avec O(Φn ). 3 Trop Poly pour être honnête P On représente un polynôme P (x) = a0 + 1≤i≤n ai xi de degré n par un tableau tab de taille n + 1 tel que tab[i]=ai (0 ≤ i ≤ n). 3.1 Proposez un algorithme permettant de calculer la somme de deux polynômes P (x) et Q(x) de même degré n. Quel est sa complexité ? La complexité de cet algorithme est clairement en O(n). 3.2 Proposez un algorithme permettant de calculer le produit de deux polynômes P (x) et Q(x) de même degré n. Quel est sa complexité ? 5 Algorithme 4 Algorithme Fibonacci(N) Données N Début Si N ≤ 1 Alors Rendre 1 fib1 = 1 fib2 = 1 fib=0 de i=2 a i=n faire fib=fib1+fib2 fib2 = fib1 fib1 = fib Rendre fib Fin Algorithme 5 somme de deux polynômes Données n degré max des polynômes P, Q les deux tableaux correspondant aux polynômes Début de i=0 a i=n faire R[i] = P[i]+Q[i] Rendre R Fin Algorithme 6 Produit de deux polynômes Données n degré max des polynômes P, Q les deux tableaux correspondant aux polynômes Début de i=0 a i=2n faire R[i] = 0 de i=0 a i=n faire de j=0 a i=n faire R[i+j] = R[i+j] + P[i]*Q[j] Rendre R Fin 6 La complexité est en O(n2 ). La première boucle est en effet en O(n) et la double boucle en O(n2 ) puisque les opérations effectuées dans chacune des boucles est en O(1). 3.3 Proposez un algorithme permettant de calculer la valeur d’un polynôme P (x) de degré n pour une valeur x0 donnée. On pourra utiliser la fonction puissance(x,p) qui calcule la valeur de xp . Quel est la complexité de cet algorithme ? Algorithme 7 Évaluation d’un polynôme Données n degré max du polynôme x valeur P le tableau correspondant au polynôme Début eval = 0 de i=0 a i=n faire eval = eval + P[i]*puissance(x,i) Rendre eval Fin Òn suppose ici que puissance(x,0) = 1 quelque soit x. La complexité de cette algorithme est lié à la complexité de puissance, qui effectue i multiplications pour évaluer puissance(x,i). L’algorithme effectue ainsi de l’ordre de 1 + 2 + 3 + ... + n opérations. Comme 1+2+3+...+n = n(n+1)/2, la complexité de l’algorithme est O(n2 ). 3.4 Méthode de Horner La méthode de Horner consiste à écrire un polynôme P (x) = a0 + P i 1≤i≤n ai x de la façon suivante : P (x) = a0 + x(a1 + ...x(an−1 + an x)...). Ainsi, le polynôme P (x) = 1 + 2x − 6x2 + 3x3 + x4 s’écrira P (x) = 1 + x(2 + x(−6 + x(3 + x))). Cette utilisation finaude du parenthèsage va nous permettre – pardon – vous permettre de créer un algorithme d’évaluation d’un polynôme avec une complexité moindre que celui de la partie 3.3. Cet algorithme permet de ne pas recalculer les puissances de x à chaque fois. De là, la complexité de l’algorithme devient O(n). 7 Algorithme 8 Évaluation d’un polynôme, le retour Données n degré max du polynôme x valeur P le tableau correspondant au polynôme Début eval = P[n] de i=n a i=0 faire eval = P[i]+ eval*x Rendre eval Fin 8