Comment puis-je calculer la complexité temporelle d'un algorithme récursif?
int pow1(int x,int n) {
if(n==0){
return 1;
}
else{
return x * pow1(x, n-1);
}
}
int pow2(int x,int n) {
if(n==0){
return 1;
}
else if(n&1){
int p = pow2(x, (n-1)/2)
return x * p * p;
}
else {
int p = pow2(x, n/2)
return p * p;
}
}
L'analyse des fonctions récursives (ou même leur évaluation) est une tâche non triviale. Une bonne introduction (à mon avis) peut être trouvée dans Don Knuths Concrete Mathematics .
Cependant, analysons maintenant ces exemples:
Nous définissons une fonction qui nous donne le temps nécessaire à une fonction. Disons que t(n)
désigne le temps nécessaire à pow(x,n)
, c'est-à-dire une fonction de n
.
Ensuite, nous pouvons conclure que t(0)=c
, car si nous appelons pow(x,0)
, nous devons vérifier si (n==0
), Puis retourner 1, ce qui peut être fait en temps constant (d'où la constante c
).
Considérons maintenant l'autre cas: n>0
. Ici, nous obtenons t(n) = d + t(n-1)
. C'est parce que nous devons à nouveau vérifier n==1
, Calculer pow(x, n-1
, Donc (t(n-1)
), et multiplier le résultat par x
. La vérification et la multiplication peuvent se faire en temps constant (constante d
), le calcul récursif de pow
a besoin de t(n-1)
.
Maintenant, nous pouvons "développer" le terme t(n)
:
t(n) =
d + t(n-1) =
d + (d + t(n-2)) =
d + d + t(n-2) =
d + d + d + t(n-3) =
... =
d + d + d + ... + t(1) =
d + d + d + ... + c
Alors, combien de temps faut-il pour atteindre t(1)
? Puisque nous commençons à t(n)
et que nous soustrayons 1 à chaque étape, il faut n-1
Étapes pour atteindre t(n-(n-1)) = t(1)
. Cela, d'autre part, signifie que nous obtenons n-1
Fois la constante d
et t(1)
est évaluée à c
.
On obtient donc:
t(n) =
...
d + d + d + ... + c =
(n-1) * d + c
Nous obtenons donc cette t(n)=(n-1) * d + c
qui est l'élément de O (n).
pow2
Peut être fait en utilisant théorème des maîtres . Puisque nous pouvons supposer que les fonctions temporelles des algorithmes augmentent de façon monotone. Nous avons donc maintenant le temps t(n)
nécessaire pour le calcul de pow2(x,n)
:
t(0) = c (since constant time needed for computation of pow(x,0))
pour n>0
nous obtenons
/ t((n-1)/2) + d if n is odd (d is constant cost)
t(n) = <
\ t(n/2) + d if n is even (d is constant cost)
Ce qui précède peut être "simplifié" pour:
t(n) = floor(t(n/2)) + d <= t(n/2) + d (since t is monotonically increasing)
Nous obtenons donc t(n) <= t(n/2) + d
, qui peut être résolu en utilisant le théorème des maîtres pour t(n) = O(log n)
(voir la section Application aux algorithmes populaires dans le lien wikipedia, exemple "Recherche binaire").
Commençons par pow1, car c'est le plus simple.
Vous avez une fonction où une seule exécution est effectuée dans O (1). (La vérification de l'état, le retour et la multiplication sont à temps constant.)
Il ne vous reste alors que votre récursivité. Ce que vous devez faire est d'analyser la fréquence à laquelle la fonction finit par s'appeler. Dans pow1, cela se produira N fois. N * O (1) = O (N).
Pour pow2, c'est le même principe - une seule exécution de la fonction s'exécute dans O (1). Cependant, cette fois, vous divisez N par deux à chaque fois. Cela signifie qu'il s'exécutera log2 (N) fois - efficacement une fois par bit. log2 (N) * O (1) = O (log (N)).
Quelque chose qui pourrait vous aider est d'exploiter le fait que la récursivité peut toujours être exprimée comme une itération (pas toujours très simplement, mais c'est possible. Nous pouvons exprimer pow1 comme
result = 1;
while(n != 0)
{
result = result*n;
n = n - 1;
}
Vous disposez maintenant d'un algorithme itératif à la place, et vous trouverez peut-être plus facile de l'analyser de cette façon.
Cela peut être un peu complexe, mais je pense que la façon habituelle est d'utiliser Théorème du Maître .
La complexité des deux fonctions ignorant la récursivité est O (1)
Pour le premier algorithme, la complexité de pow1 (x, n) est O(n) car la profondeur de récursion est corrélée avec n linéairement.
Pour la deuxième complexité est O (log n). Ici, nous récapitulons environ log2 (n) fois. En jetant 2, nous obtenons log n.
Je suppose donc que vous élevez x à la puissance n. pow1 prend O (n).
Vous ne changez jamais la valeur de x mais vous prenez 1 de n à chaque fois jusqu'à ce qu'il atteigne 1 (et vous revenez ensuite) Cela signifie que vous effectuerez un appel récursif n fois.