Cela dépend de la rigueur avec laquelle vous définissez "récursivité".
Si nous exigeons strictement que la pile d'appels soit impliquée (ou quel que soit le mécanisme utilisé pour maintenir l'état du programme), nous pouvons toujours le remplacer par quelque chose qui ne le fait pas. En effet, les langages qui conduisent naturellement à un usage intensif de la récursivité ont tendance à avoir des compilateurs qui font un usage intensif de l'optimisation de l'appel final, de sorte que ce que vous écrivez est récursif mais que vous exécutez est itératif.
Mais prenons le cas où nous effectuons un appel récursif et utilisons le résultat d'un appel récursif pour cet appel récursif.
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
if (m == 0)
return n+1;
if (n == 0)
return Ackermann(m - 1, 1);
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
Rendre le premier appel récursif itératif est facile:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
restart:
if (m == 0)
return n+1;
if (n == 0)
{
m--;
n = 1;
goto restart;
}
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
Nous pouvons ensuite nettoyer enlever le goto
pour éviter les vélociraptors et l'ombre de Dijkstra:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
while(m != 0)
{
if (n == 0)
{
m--;
n = 1;
}
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
return n+1;
}
Mais pour supprimer les autres appels récursifs, nous allons devoir stocker les valeurs de certains appels dans une pile:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
Stack<BigInteger> stack = new Stack<BigInteger>();
stack.Push(m);
while(stack.Count != 0)
{
m = stack.Pop();
if(m == 0)
n = n + 1;
else if(n == 0)
{
stack.Push(m - 1);
n = 1;
}
else
{
stack.Push(m - 1);
stack.Push(m);
--n;
}
}
return n;
}
Maintenant, quand nous considérons le code source, nous avons certainement transformé notre méthode récursive en une méthode itérative.
Compte tenu de ce qui a été compilé, nous avons transformé le code qui utilise la pile d'appels pour implémenter la récursivité dans le code qui ne fonctionne pas (et ce faisant, nous avons transformé le code qui lève une exception de débordement de pile pour des valeurs même très petites dans un code qui se contente de prendre un temps pour revenir longtemps atrocement [voir Comment puis - je empêcher ma fonction Ackerman de déborder la pile? quelques autres qui font optimisations le retourner en fait pour beaucoup plus entrées possibles]).
Considérant la manière dont la récursivité est généralement implémentée, nous avons transformé le code qui utilise la pile d'appels en code utilisant une pile différente pour contenir les opérations en attente. Nous pourrions donc affirmer qu'il est toujours récursif, à ce niveau bas.
Et à ce niveau, il n'y a en effet pas d'autre moyen de le contourner. Donc, si vous considérez que cette méthode est récursive, il y a en effet des choses que nous ne pouvons pas faire sans. Généralement, nous n'indiquons pas ce code récursif. Le terme récursion est utile car il recouvre un certain ensemble d’approches et nous permet d’en parler, et nous n’utilisons plus l’une d’elles.
Bien sûr, tout cela suppose que vous avez le choix. Il existe des langues interdisant les appels récursifs et des langues dépourvues des structures de boucle nécessaires à l'itération.