Oui, c'est ce que faisait déjà le compilateur Le_Lisp (dans les années 70-80). Comme le pseudo-assembleur virtuel LLM3 généré était "facile" à lire, on pouvait vérifier que le compilateur utilisait un "goto" (sans pile) et non un "gosub" (avec empilement du contexte).
Attention toutefois à certains pièges. J'ai dit:
Ce code n'est pas récursif terminal, contrairement à ce qu'il pourrait sembler à première vue!
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 define factorielle (N) if N == 0 then return 1 else return N * factorielle (N - 1) fi
(voir le premier contre-exemple de http://fr.wikipedia.org/wiki/Récursion_terminale)
En effet, bien que l'appel de factorielle (N - 1) semble apparaitre en dernier dans le code, il faut d'abord calculer N et factorielle (N - 1) avant de les multiplier entre eux. Dans ce cas, le compilateur ne peut pas se passer de pile (pour empiler N et la référence de l'appel à la multiplication, puis N-1 et la référence de l'appel à la multiplication, etc.)!
Je serais surpris, voire impressionné, si un compilateur arrivait à transformer ce code en itération!
Alors qu'un bête être humain le fait sans problème en énumérant les nombres à multiplier (soit de 1 à N, soit de N à 1):
D'ailleurs, dans toutes les formules données par récurrence (au sens mathématique), comme factorielle ou fibonacci, il est plus simple de calculer les valeurs dans l'ordre croissant, en mémorisant/stockant celles qui seront utiles pour le calcul de l'élément suivant.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 define factorielle (N) RESULT := 1 for I from 1 to N do RESULT *= I I++ done return RESULT
Ainsi, il suffit de garder (dans un coin) factorielle(n) pour calculer factorielle(n+1).
Il suffit de garder (dans 2 coins) fibo(n-1) et fibo(n) pour calculer le suivant fibo(n+1). (voir algo précédemment donné)
Évidemment, dans des cas plus compliqués, comme la fonction d'Ackermann (ou d'autres fonctions à plus d'un argument), le nombre de coins peut exploser!
Ce procédé peut d'ailleurs plus ou moins être automatisé en remplaçant les fonctions par des "fonctions-mémoire", qui se souviennent de tous les éléments déjà calculés et retournent la valeur calculée et stockée plutôt que de la recalculer.
Pour la factorielle, ce n'est pas intéressant si on n'a qu'un seul calcul de factorielle à effectuer. En revanche, si on en a un 2ème, si le nombre est plus petit, le résultat est immédiatement retourné, et s'il est plus grand, seules les multiplications supplémentaires seront effectuées.
Pour fibonacci, c'est intéressant dès le premier calcul:
Ce code (qui reste récursif) ne fait que 2*N additions, alors que, dans le code récursif traditionnel, le nombre d'additions est exponentiel (de l'ordre de ((sqrt(5)+1)/2)**N probablement (à vérifier...)).
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 define fibonacci (N) if is_stored_fibonacci(N) then return stored_value_fibonacci(N) elsif N == 0 then store_fibonacci(0, 1) return 1 elsif N == 1 then store_fibonacci(1, 1) return 1 else return fibonacci (N - 1) + fibonacci (N - 2) fi
Partager