Depende de quão estritamente você define "recursão".
Se exigirmos estritamente que envolva a pilha de chamadas (ou qualquer mecanismo para manter o estado do programa usado), sempre poderemos substituí-lo por algo que não o faça. De fato, as linguagens que levam naturalmente ao uso pesado de recursão tendem a ter compiladores que fazem uso intenso da otimização de chamada de cauda; portanto, o que você escreve é recursivo, mas o que você executa é iterativo.
Mas vamos considerar um caso em que fazemos uma chamada recursiva e usamos o resultado de uma chamada recursiva para essa chamada recursiva.
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));
}
É fácil fazer a primeira chamada recursiva iterativa:
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));
}
Em seguida, podemos limpar o dispositivo goto
para afastar os velociraptores e a sombra 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;
}
Mas, para remover as outras chamadas recursivas, teremos que armazenar os valores de algumas chamadas em uma pilha:
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;
}
Agora, quando consideramos o código fonte, certamente transformamos nosso método recursivo em um método iterativo.
Considerando o que isso foi compilado, transformamos o código que usa a pilha de chamadas para implementar recursão em código que não o faz (e, ao fazer isso, transformou o código que lançará uma exceção de excesso de pilha para valores muito pequenos em código que apenas demore muito tempo para retornar [consulte Como posso impedir que minha função Ackerman transborde a pilha? para algumas otimizações adicionais que fazem com que ela realmente retorne para muitas outras entradas possíveis]).
Considerando como a recursão é implementada geralmente, transformamos o código que usa a pilha de chamadas em código que usa uma pilha diferente para manter operações pendentes. Poderíamos, portanto, argumentar que ainda é recursivo, quando considerado nesse nível baixo.
E nesse nível, de fato não há outras maneiras de contornar isso. Portanto, se você considera esse método recursivo, há realmente coisas que não podemos fazer sem ele. Geralmente, embora não rotulemos esse código como recursivo. O termo recursão é útil porque cobre um certo conjunto de abordagens e nos dá uma maneira de falar sobre elas, e não estamos mais usando uma delas.
Claro, tudo isso pressupõe que você tem uma escolha. Existem dois idiomas que proíbem chamadas recursivas e idiomas que não possuem as estruturas de loop necessárias para a iteração.