Це залежить від того, наскільки суворо ви визначаєте "рекурсію".
Якщо ми суворо вимагаємо, щоб він включав стек викликів (або будь-який механізм збереження стану програми), ми завжди можемо замінити його чимось, що не відповідає. Дійсно, мови, які природно призводять до важкого використання рекурсії, зазвичай мають компілятори, які широко використовують оптимізацію хвостових викликів, тому те, що ви пишете, є рекурсивним, але те, що ви запускаєте, є ітераційним.
Але давайте розглянемо випадок, коли ми робимо рекурсивний виклик і використовуємо результат рекурсивного виклику для цього рекурсивного дзвінка.
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));
}
Зробити перший рекурсивний ітеративний дзвінок просто:
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));
}
Тоді ми можемо прибрати прибирання, goto
щоб позбавити велоцирапторів і відтінок 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;
}
Але для видалення інших рекурсивних викликів нам доведеться зберігати значення деяких дзвінків у стек:
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;
}
Тепер, коли ми розглядаємо вихідний код, ми, безумовно, перетворили наш рекурсивний метод на ітеративний.
Враховуючи, для чого це складено, ми перетворили код, який використовує стек виклику, щоб реалізувати рекурсію в код, який цього не робить (і при цьому перетворив код, який передасть виняток переповнення стека для навіть зовсім невеликих значень у код, який буде просто прийняти болісно довго , щоб повернутися [см Як я можу запобігти мою функцію Акерман від переповнення стека? для деяких додаткових оптимізацій , які роблять це на самому справі повернутися на багато більше можливих входів]).
Зважаючи на те, як реалізується рекурсія загалом, ми перетворили код, який використовує стек викликів, у код, який використовує інший стек для проведення очікуваних операцій. Тому ми можемо стверджувати, що вона все ще є рекурсивною, якщо розглядати її на такому низькому рівні.
І на тому рівні насправді немає інших шляхів навколо цього. Тож якщо ви вважаєте цей метод рекурсивним, то справді ми не можемо обійтися без нього. Як правило, ми не позначаємо такий код рекурсивним. Термін « рекурсія» є корисним, оскільки охоплює певний набір підходів і дає нам можливість говорити про них, і ми більше не використовуємо один з них.
Звичайно, все це передбачає, що у вас є вибір. Існують як мови, що забороняють рекурсивні дзвінки, так і мови, у яких відсутні циклічні структури, необхідні для ітерації.