La notation asymptotique François Lemieux Hiver 2007 Nous avons vu que le temps d’exécution d’un algorithme A pouvait être exprimé comme une fonction T : N→R+ telle que T (n) représente le temps maximal que prend A sur une entrée de longueur n. Si nous voulons pouvoir comparer les temps d’exécution de plusieurs algorithmes, il est d’abord nécessaire de savoir comparer les fonctions entre elles. Afin d’atteindre cet objectif, nous allons définir une notation qui sera d’une très grande utilisé par la suite. 1 La notation “grand O” La notation grand O sert à exprimer le fait que l’ordre de grandeur d’une fonction est inférieur ou égal à une autre. Définition 1 Soit f : N→R+ une fonction positive. On défini l’ordre de f (n) comme l’ensemble: O(f ) = {g : N→R+ | (∃c > 0)(∃n0 ≥ 0)(∀n ≥ n0 )[g(n) ≤ cf (n)]} Pour montrer qu’une fonction f (n) est dans l’ordre d’une autre fonction g(n), on doit donc trouver deux constantes c et n0 telles que f (n) ≤ cg(n) est vrai sauf, possiblement, pour des petites valeurs inférieures à n0 . Exemple. Pour montrer que n2 ∈ O(5n2 + 3n + 4) on doit trouver deux constantes c > 0 et n0 ≥ 0 tels que n2 ≤ 5n2 + 3n + 4. Pour ce faire, on 1 observe que puisque 3n + 4 > 0 alors n2 ≤ 5n2 + 3n + 4 quelque soit la valeur de n ≥ 0. Il suffit donc de prendre n0 = 0 et c = 1. Exemple. Montrons qu’on a aussi 5n2 + 3n + 4 ∈ O(n2 ). On a 5n2 + 3n + 4 ≤ 5n2 + 3n2 + 4n2 = 12n2 L’inégalité se vérifiant pour tout n ≥ 1. Donc, si on pose c = 12 et n0 = 1 on obtient (∀n ≥ n0 )[5n2 + 3n + 4 ≤ cn2 ] Ce qui prouve que 5n2 + 3n + 4 ∈ O(n2 ). Les deux exemples précédent peuvent facilement être généralisés: Lemme 1 Soit p(n) un polynôme de degré d ≥ 0. Alors p(n) ∈ O(nd ) et nd ∈ O(p(n)). Preuve. Montrons d’abord que p(n) ∈ O(nd ). On a p(n) = a0 , a1 , . . . ad sont des constantes. On peut donc écrire: p(n) = d X ai ni ≤ i=0 d X |ai |ni ≤ i=0 d X Pd i=0 ai ni où |ai |nd i=0 La dernière inégalité étant vrai pour tous les entiers n ≥ 1. Si on pose Pd c = i=0 |ai | et n0 = 1 on obtient que p(n) ≤ cnd pour tout n ≥ n0 . Cela démontre que: (∃c > 0)(∃n0 ≥ 0)(∀n ≥ n0 )[p(n) ≤ cnd ] et qu’ainsi p(n) ∈ O(nd ). Il reste à montrer que nd ∈ O(p(n)). Nous devons trouver deux constantes c > 0 et n0 ≥ 0 telles que pour tout n ≥ n0 l’inégalité nd ≤ cp(n) est vérifiée. En fait, il est plus simple de montrer que p(n) ≥ nd /c. On a: p(n) = d X i=0 i d ai n = ad n + d−1 X i d ai n ≥ ad n − i=0 d−1 X i=0 2 i d |ai |n ≥ ad n − n d−1 d−1 X i=0 |ai | La dernièrePinégalité est vrai pour tout n ≥ 1. Finalement, pour tout n ≥ max {1, a2d d−1 i=0 |ai |}, on a aussi: d ad n − n d−1 d−1 X |ai | ≥ ad nd − i=0 En prenant c = 2 ad et n0 = max {1, a2d ad d ad d n = n 2 2 Pd−1 i=0 |ai |} on obtient: (∀n ≥ n0 )[nd ≤ c · p(n)] Ce qui démontre que nd ∈ O(p(n)). Le lemme suivant est très utile et nous permettra, entre autres, de démontrer que si p(n) et q(n) sont deux polynômes de même degré alors O(p(n)) = O(q(n)). Pour cette raison on ne fera aucune différence entre deux polynômes de même degré. Plus généralement, si f, g : N→R+ sont deux fonctions telles que O(f ) = O(g) alors on ne fera aucune distinction entre f et g. Lemme 2 Soit f, g, h : N→R+ trois fonctions positives. Si f ∈ O(g) et g ∈ O(h) alors f ∈ O(h). Preuve. Puisque f ∈ O(g) et g ∈ O(h) alors on a: 1. (∃c1 > 0)(∃n1 ≥ 0)(∀n ≥ n1 )[f (n) ≤ c1 g(n)] 2. (∃c2 > 0)(∃n2 ≥ 0)(∀n ≥ n2 )[g(n) ≤ c2 h(n)] Puisque le premier énoncé est vrai pour tout n ≥ n1 et que le second est vrai pour tout n ≥ n2 alors les deux énoncés sont vrais pour tout n ≥ max{n1 , n2 }. On obtient donc que pour tout n ≥ max{n1 , n2 }: f (n) ≤ c1 g(n) ≤ c1 c2 h(n) Si on pose n0 = max{n1 , n2 } et c = c1 c2 , on obtient: (∃c > 0)(∃n0 ≥ 0)(∀n ≥ n0 )[f (n) ≤ ch(n)] On conclu que f (n) ∈ O(h(n)). 3 Corollaire 1 Soit f, g : N→R+ . Si f ∈ O(g) alors O(f ) ⊆ O(g). Preuve. Supposons que f ∈ O(g). Pour montrer que O(f ) ⊆ O(g) il faut montrer que pour tout h ∈ O(f ) on a h ∈ O(g), ce qui est vrai par le Lemme 2. Exemple. Poursuivant avec l’exemple précédent, le lemme 1 nous indique que 5n2 − 3n − 4 ∈ O(n2 ) et n2 ∈ O(5n2 − 3n − 4). Utilisant le corollaire 1 nous concluons que O(5n2 − 3n − 4) = O(n2 ). Proposition 2 Soit p(n) et q(n) deux polynômes de degré d ≥ 0. Alors O(p(n)) = O(q(n)). Preuve. Par le lemme 1 nous savons que p(n) ∈ O(nd ) et que nd ∈ O(q(n)). Par le lemme 2, on a p(n) ∈ O(q(n)). Finalement, par le corollaire 1, on a O(p(n)) ⊆ O(q(n)). Similairement, on démontre que O(q(n)) ⊆ O(p(n)) ce qui prouve la proposition. Tout comme on ne fera aucune différence entre deux polynômes de même degré, on ne fera aucune distinction entre deux fonctions logarithmiques utilisant des bases différentes. Proposition 3 Pour toutes valeurs a, b > 0 on a O(loga n) = O(logb n). Preuve. Nous devons montrer que: 1. O(loga n) ⊆ O(logb n) 2. O(logb n) ⊆ O(loga n) Seule la démonstration de la première inclusion est nécessaire: la preuve de la seconde étant identique. En fait, par le corollaire 1, il suffit de montrer que loga n ∈ O(logb n). On sait que: loga n = Donc, en choisissant c = 1 logb a logb n logb a et n0 = 1 on a: (∀n ≥ n0 )[loga n ≤ c logb n] 4 Ainsi, on a bien loga n ∈ O(logb n). Remarque: Il faut faire attention de ne pas tirer de fausses conclusions de la proposition précédentente. Par exemple si a < b on a 2logb n ⊆ O(2loga n ) mais O(2loga n ) 6= O(2logb n ). 2 Analyse asymptotique Exemple. Considérez le segment de code C suivant: x=0; for (i=1; i<=n; i++) x++; for (j=1; j<=n; j=j*2) x++; Le tableau suivant analyse le temps d’exécution de chacune de ces cinq lignes de code. ligne 1 2 3 4 5 temps/exécution c1 c2 c3 c4 c5 nb exécutions 1 n+1 n blg nc + 1 blg nc ordre O(1) O(n) O(n) O(lg n) O(lg n) On suppose qu’une seule exécution de la ligne i nécessite un temps constant inconnu que nous dénotons ci (i = 1, 2, 3, 4, 5). Le nombre de fois que chacune des lignes est exécutée est indiqué dans la troisième colonne. La dernière colonne donne l’ordre du temps total d’exécution pour chacune des lignes. Le temps total d’exécution du segment est: T (n) = = = ∈ c1 + c2 (n + 1) + c3 n + c4 (blg nc + 1) + c5 blg nc (c1 + c2 + c4 ) + (c2 + c3 )n + (c4 + c5 )blg nc d1 + d2 n + d3 blg nc O(n) 5 Cet exemple démontre que l’estimation du temps d’exécution de chacune des lignes se fait beaucoup plus simplement si l’on utilise la notation assymptotique. Cependant, cette méthode ne sera utile que si elle permet de calculer plus facilement le temps total d’exécution T (n) du segment de code. Par exemple, il serait utile de pouvoir écrire: T (n) = O(1) + O(n) + O(n) + O(lg n) + O(lg n) = O(1 + n + n + lg n + lg n) = O(n) Intuitivement, ce type d’arithmétique semble correct sauf que O(n) et O(lg n) sont des ensembles et que l’addition d’ensembles n’est pas définie. Ce problème peut facilement être corrigé lorsqu’il est question, comme ici, d’ensembles de fonctions: Définition 2 Soit f, g : N→R+ . (a) O(f ) + O(g) = {h : N→R+ | ∃h1 ∈ O(f ), ∃h2 ∈ O(g), h(n) = h1 (n) + h2 (n)} (b) O(f )O(g) = {h : N→R+ | ∃h1 ∈ O(f ), ∃h2 ∈ O(g), h(n) = h1 (n)h2 (n)} La définition du produit de deux ensembles de fonctions est justifiée par l’exemple suivant: Exemple. Considérez le segment de code C suivant: x=0; for (i=1; i<=n/2; i++) for (j=1; j<=n; j=j*2) x++; L’analyse de ce code est effectuée dans le tableau suivant: ligne 1 2 3 4 temps/exécution c1 c2 c3 c4 nb exécutions 1 bn/2c + 1 bn/2c(blg nc + 1) bn/2c(blg nc) 6 ordre O(1) O(n) O(n)O(lg n) O(n)O(lg n) Si l’on considère l’ordre du nombre d’exécutions de la dernière ligne, on observe que la première instruction for effectue O(n) itérations et qu’à chacune d’elles la seconde instruction for effectue O(lg n) itérations. Il est donc naturel d’exprimer le nombre total d’exécutions de la dernière ligne sous la forme O(n)O(lg n). Même après avoir défini l’addition et le produit d’ensembles de fonctions il reste à se convaincre que l’arithmétique des ensembles que nous voulons utiliser est cohérente et qu’elle fonctionne dans toutes les situations. C’est ce qui résulte des deux lemmes suivants: Lemme 3 Soit f, g : N→R+ , deux fonctions positives. (a) O(f ) + O(g) = O(f + g) (b) O(f )O(g) = O(f g) Preuve. Nous allons seulement démontrer (b) puisque la preuve de (a) est similaire. Montrons d’abord que O(f )O(g) ⊆ O(f g). Soit h ∈ O(f )O(g). Alors il existe deux fonctions h1 ∈ O(f ) et h2 ∈ O(g) telles que h(n) = h1 (n)h2 (n). On a alors 1. (∃c1 > 0)(∃n1 ≥ 0)(∀n ≥ n1 )[h1 (n) ≤ c1 f (n)}] 2. (∃c2 > 0)(∃n2 ≥ 0)(∀n ≥ n2 )[h2 (n) ≤ c2 g(n)}] Donc, pour tout n ≥ max {n1 , n2 } on a : h(n) = h1 (n)h2 (n) ≤ c1 c2 f (n)g(n) Ce qui montre que h(n) ∈ O(f g). Montrons maintenant que O(f g) ⊆ O(f )O(g). Soit h(n) ∈ O(f (n)g(n)). On a alors deux constantes c > 0 et n0 ≥ 0 telles que h(n) ≤ cf (n)g(n) pour tout n ≥ n0 . Définissons deux fonctions h1 (n) et h2 (n) telles que h1 (n) = f (n) et ½ h(n)/f (n) si f (n) 6= 0 h2 (n) = 0 sinon On a donc h(n) = h1 (n)h2 (n) et h1 (n) ∈ O(f (n)). On a aussi h2 (n) ∈ O(g(n)) puisque: h(n) = h1 (n)h2 (n) = f (n)h2 (n) ≤ cf (n)g(n) =⇒ h2 (n) ≤ cg(n) 7 On conclu que h(n) ∈ O(f (n))O(g(n)). Le prochain lemme sera utile pour simplifier les fonctions exprimant le temps d’exécution d’un algorithme (ou son espace mémoire). Lemme 4 Si f (n) ∈ O(g(n)) alors O(f (n) + g(n)) = O(g(n)) Exemple. Poursuivant avec le dernier exemple, le temps total du segment de code est: T (n) = O(1) + O(n) + O(n)O(lg n) + O(n)O(lg n) = O(1 + n + 2n lg n) = O(n lg n) 3 Les autres notations Intuitivement, on utilise la relation grand O entre les fonctions un peu comme on utilise la relation ≤ avec les entiers. On peut aussi définir quatre autres relations dont le rôle pour les fonctions est similaire aux relations d’entiers: ≥, =, < et >. Définition 3 Soit f : N→R+ . 1. Ω(f (n)) = {g : N→R+ | f (n) ∈ O(g(n))} 2. Θ(f (n)) = {g : N→R+ | O(g(n)) = O(f (n))} 3. o(f (n)) = O(f (n) − Θ(f (n)) 4. ω(f (n)) = Ω(f (n)) − Θ(f (n)) Exemple. Soit p1 (n) et p2 (n) deux polynômes de degré d1 > 0 et d2 > 0 respectivement. Si d1 = d2 alors p1 (n) ∈ Θ(p2 (n)) mais si d1 < d2 alors p1 (n) ∈ o(p2 (n)). 8 Lemme 5 Soit f, g : N→R+ . 1. f (n) ∈ O(g(n)) si et seulement si g(n) ∈ Ω(f (n)). 2. f (n) ∈ o(g(n)) si et seulement si g(n) ∈ ω(f (n)). 3. f (n) ∈ Θ(g(n)) si et seulement si f (n) ∈ O(g(n)) ∩ Ω(g(n)). Theorem 4 Soit 0 < c < ∞. f (n) n→∞ g(n) = 0 alors f (n) ∈ o(g(n)) f (n) n→∞ g(n) = ∞ alors f (n) ∈ ω(g(n)) f (n) n→∞ g(n) = c alors f (n) ∈ Θ(g(n)) 1. Si lim 2. Si lim 3. Si lim Preuve. Nous allons simplement démontrer la première partie. (n) Si lim fg(n) = 0 alors f (n) ∈ O(g(n). Il faut donc montrer que g(n) 6∈ n→∞ O(f (n)). On a: (n) (∀² > 0)(∃n² ≥ 0)(∀n ≥ n² )[| fg(n) | ≤ ²] =⇒ (∀² > 0)(∃n² ≥ 0)(∀n ≥ n² )[f (n) ≤ ²g(n)] =⇒ (∀² > 0)(∀n0 ≥ 0)(∃n ≥ n0 )[f (n) ≤ ²g(n)] La dernière implication provient du fait que si f (n) ≤ ²g(n) est vrai pour tout n ≥ n² alors cela est vrai pour une infinité de valeur n. Cela implique que quelque soit la valeur n0 on peut toujours touver un n encore plus grand tel que f (n) ≤ ²g(n) est vrai. La dernière expression est équivalente à la négation de: (∃c > 0)(∃n0 ≥ 0)(∀n ≥ n0 )[g(n) ≤ cf (n)], où c = 1/² Ce qui démontre que g(n) 6∈ O(f (n)). 9 4 Les limites Un outil très utile pour démontrer qu’une fonction est dans l’ordre d’une autre fonction consiste à utiliser les limites. Rappelons d’abord la définition. Définition 4 La limite d’une fonction h(n) est défini comme suit. On a: lim f (n) = c n→∞ si et seulement si (∀² > 0)(∃n² ≥ 0)(∀n ≥ n² )[|f (n) − c| ≤ ²] En d’autres mots, f (n) se rapproche de plus en plus de c à mesure que n augmente. Exemple. Considérons la fonction h(n) = 1/n. Cette fonction se rapproche de plus en plus près de 0 à mesure que n augmente. On a donc 1 =0 n→∞ n lim f (n) n→∞ g(n) Lemme 6 Soit f, g : N→R+ et c ≥ 0. Si lim f (n) n→∞ g(n) Preuve. Si lim = c alors f (n) ∈ O(g(n)). = c alors on a: (n) (∀² > 0)(∃n² ≥ 0)(∀n ≥ n² )[| fg(n) − c| ≤ ²] (n) =⇒ (∀² > 0)(∃n² ≥ 0)(∀n ≥ n² )[ fg(n) ≤ ² + c] =⇒ (∀² > 0)(∃n² ≥ 0)(∀n ≥ n² )[f (n) ≤ (² + c)g(n)] =⇒ (∃d > 0)(∃n² ≥ 0)(∀n ≥ n² )[f (n) ≤ dg(n)] =⇒ f (n) ∈ O(g(n)) Exemple. On peut vérifier que n ∈ O(n2 ) en utilisant le lemme précédent. Bien sur on a: 1 n lim 2 = lim = 0 n→∞ n n→∞ n 10 Lorsque f, g : N→R+ sont deux fonctions dérivables qui tendent vers l’infini lorsque n augmente, on peut alors appliquer la règle de l’Hospital: f (n) f 0 (n) = lim 0 n→∞ g(n) n→∞ g (n) lim Exemple. Montrons que ln n ∈ O(n). Par le lemme 6, il suffit de montrer que lim lnnn = 0. En appliquant la règle de l’Hospital nous obtenons: n→∞ lim n→∞ ln n 1/(n) 1 = lim = lim = 0 n→∞ n→∞ n 1 n Cet exemple est généralisé par la proposition suivante: Proposition 5 Pour tout ² > 0 et pour tout a > 0 on a loga n ∈ O(n² ). Preuve. Par le lemme 6, il suffit de montrer que lim logna² n = 0. En applin→∞ quant la règle de l’Hospital nous obtenons: lim n→∞ loga n 1/(n ln a) 1 = lim = lim =0 ² ²−1 n→∞ n→∞ n ²n ²(ln a)n² Exemple. Montrons que n1.5 ∈ O(2n ). En appliquant deux fois la règle de l’Hospital on obtient: n1.5 (1.5)n0.5 (0.5)(1.5)n−0.5 = lim = lim n→∞ 2n n→∞ (ln 2)2n n→∞ (ln 2)2 2n lim On a donc (0.5)(1.5)n−0.5 (0.75) √ =0 = lim n→∞ n→∞ (ln 2)2 2n n (ln 2)2 2n lim Cet exemple peut être généralisé par la proposition suivante. 11 Proposition 6 Soit d ≥ 0 et r > 1 deux constantes réelles. Alors nd ∈ O(rn ). Preuve. Soit k = dde. Si on dérive k fois la fonction f (n) = nd , on obtient la fonction f (k) (n) = d(d − 1) · · · (d − k + 1)nd−k où d − k ≤ 0. Si on dérive k fois la fonction g(n) = rn , on obtient la fonction g (k) (n) = (ln r)k an . Donc, en appliquant k fois la règle de l’Hospital on obtient: f (n) f (k) (n) d(d − 1) · · · (d − k + 1)nd−k = lim (k) = lim n→∞ g(n) n→∞ g (n) n→∞ (ln r)k an lim d(d − 1) · · · (d − k + 1) =0 n→∞ (ln r)k an nk−d = lim 12