Étant donné une fonction arbitrairement double récursive, comment calculer son temps d'exécution?
Par exemple (en pseudocode):
int a(int x){
if (x < = 0)
return 1010;
else
return b(x-1) + a(x-1);
}
int b(int y){
if (y <= -5)
return -2;
else
return b(a(y-1));
}
Ou quelque chose de ce genre.
Quelles méthodes pourrait-on ou devrait-on utiliser pour déterminer quelque chose comme ça?
Réponses:
Vous continuez de changer votre fonction. Mais continuez à choisir ceux qui fonctionneront pour toujours sans conversion ..
La récursivité devient compliquée, rapide. La première étape de l'analyse d'une fonction doublement récursive proposée consiste à essayer de la retracer sur certaines valeurs d'échantillon, pour voir ce qu'elle fait. Si votre calcul entre dans une boucle infinie, la fonction n'est pas bien définie. Si votre calcul entre dans une spirale qui continue à augmenter les nombres (ce qui se produit très facilement), il n'est probablement pas bien défini.
Si le suivi donne une réponse, vous essayez alors de trouver un modèle ou une relation de récurrence entre les réponses. Une fois que vous avez cela, vous pouvez essayer de comprendre son temps d'exécution. Le comprendre peut être très, très compliqué, mais nous avons des résultats tels que le théorème maître qui nous permettent de trouver la réponse dans de nombreux cas.
Attention, même avec une seule récursivité, il est facile de trouver des fonctions dont on ne sait pas calculer le temps d'exécution. Par exemple, considérez ce qui suit:
On ignore actuellement si cette fonction est toujours bien définie, sans parler de son exécution.
la source
L'exécution de cette paire de fonctions particulière est infinie car aucune ne revient sans appeler l'autre. La valeur de retour
a
est toujours dépendant de la valeur de retour d'un appelb
qui toujours appellea
... et c'est ce qu'on appelle une récursion infinie .la source
a
n'appelle queb
si le nombre transmis est> = 0. Mais oui, il y a une boucle infinie.La méthode la plus évidente consiste à exécuter la fonction et à mesurer le temps nécessaire. Cependant, cela ne vous indique que le temps nécessaire à une entrée particulière. Et si vous ne savez pas à l'avance que la fonction se termine, difficile: il n'y a aucun moyen mécanique de déterminer si la fonction se termine - c'est le problème de l' arrêt , et c'est indécidable.
Trouver le temps d'exécution d'une fonction est également indécidable, selon le théorème de Rice . En fait, le théorème de Rice montre que même décider si une fonction s'exécute dans le
O(f(n))
temps est indécidable.Donc, le mieux que vous puissiez faire en général est d'utiliser votre intelligence humaine (qui, à notre connaissance, n'est pas liée par les limites des machines de Turing) et d'essayer de reconnaître un modèle ou d'en inventer un. Une manière typique d'analyser le temps d'exécution d'une fonction consiste à transformer la définition récursive de la fonction en une équation récursive sur son temps d'exécution (ou un ensemble d'équations pour les fonctions récurrentes mutuellement):
Et ensuite? Vous avez maintenant un problème mathématique: vous devez résoudre ces équations fonctionnelles. Une approche qui fonctionne souvent consiste à transformer ces équations sur les fonctions entières en équations sur les fonctions analytiques et à utiliser le calcul pour les résoudre, en interprétant les fonctions
T_a
et enT_b
tant que fonctions génératrices .Sur la génération de fonctions et d'autres sujets mathématiques discrets, je recommande le livre Concrete Mathematics , de Ronald Graham, Donald Knuth et Oren Patashnik.
la source
Comme d'autres l'ont souligné, l'analyse de la récursivité peut devenir très difficile très rapidement. Voici un autre exemple d'une telle chose: http://rosettacode.org/wiki/Mutual_recursion http://en.wikipedia.org/wiki/Hofstadter_sequence#Hofstadter_Female_and_Male_sequences il est difficile de calculer une réponse et un temps d'exécution pour ceux-ci. Cela est dû à ces fonctions mutuellement récursives ayant une "forme difficile".
Quoi qu'il en soit, regardons cet exemple simple:
http://pramode.net/clojure/2010/05/08/clojure-trampoline/
Commençons par essayer de calculer
funa(m), m > 0
:Le temps d'exécution est:
Prenons maintenant un autre exemple, un peu plus compliqué:
Inspiré par http://planetmath.org/encyclopedia/MutualRecursion.html , qui est une bonne lecture en soi, regardons: "" "Les nombres de Fibonacci peuvent être interprétés par récurrence mutuelle: F (0) = 1 et G (0 ) = 1, avec F (n + 1) = F (n) + G (n) et G (n + 1) = F (n). "" "
Alors, quel est le temps d'exécution de F? Nous irons dans l'autre sens.
Eh bien, R (F (0)) = 1 = F (0); R (G (0)) = 1 = G (0)
Maintenant R (F (1)) = R (F (0)) + R (G (0)) = F (0) + G (0) = F (1)
...
Il n'est pas difficile de voir que R (F (m)) = F (m) - par exemple, le nombre d'appels de fonction nécessaires pour calculer un nombre de Fibonacci à l'indice i est égal à la valeur d'un nombre de Fibonacci à l'indice i. Cela supposait que l'addition de deux nombres ensemble était beaucoup plus rapide qu'un appel de fonction. Si ce n'était pas le cas, alors ce serait vrai: R (F (1)) = R (F (0)) + 1 + R (G (0)), et l'analyse de cela aurait été plus compliquée, éventuellement sans solution de forme fermée facile.
La forme fermée de la séquence de Fibonacci n'est pas forcément facile à réinventer, sans parler d'exemples plus compliqués.
la source
La première chose à faire est de montrer que les fonctions que vous avez définies se terminent et pour quelles valeurs exactement. Dans l'exemple que vous avez défini
b
ne se termine quey <= -5
parce que si vous insérez une autre valeur, vous aurez un terme du formulaireb(a(y-1))
. Si vous faites un peu plus d'expansion, vous verrez qu'un terme du formulaireb(a(y-1))
mène finalement au termeb(1010)
qui mène à un termeb(a(1009))
qui mène à nouveau au termeb(1010)
. Cela signifie que vous ne pouvez pas brancher de valeura
qui ne satisfait pas,x <= -4
car si vous le faites, vous vous retrouvez avec une boucle infinie où la valeur à calculer dépend de la valeur à calculer. Donc, essentiellement, cet exemple a un temps d'exécution constant.Donc, la réponse simple est qu'il n'y a pas de méthode générale pour déterminer le temps d'exécution des fonctions récursives car il n'y a pas de procédure générale qui détermine si une fonction définie de manière récursive se termine.
la source
Runtime comme dans Big-O?
C'est simple: O (N) - en supposant qu'il existe une condition de terminaison.
La récursivité est simplement une boucle, et une simple boucle est O (N), peu importe le nombre de choses que vous faites dans cette boucle (et appeler une autre méthode n'est qu'une autre étape de la boucle).
Là où cela deviendrait intéressant, c'est si vous avez une boucle dans une ou plusieurs des méthodes récursives. Dans ce cas, vous vous retrouveriez avec une sorte de performance exponentielle (multipliant par O (N) à chaque passage dans la méthode).
la source
O(2^n)
etO(n*log(n))
, respectivement.a
appelleb
etb
appellea
, vous ne pouvez donc pas simplement supposer que l'une ou l'autre méthode prend du tempsO(1)
.