Itu tergantung pada seberapa ketat Anda mendefinisikan "rekursi".
Jika kita benar-benar mengharuskannya untuk melibatkan tumpukan panggilan (atau mekanisme apa pun untuk mempertahankan status program yang digunakan), maka kita selalu dapat menggantinya dengan sesuatu yang tidak. Memang, bahasa yang secara alami mengarah pada penggunaan rekursi yang berat cenderung memiliki kompiler yang menggunakan optimisasi panggilan ekor, sehingga apa yang Anda tulis bersifat rekursif tetapi apa yang Anda jalankan adalah berulang.
Namun mari kita pertimbangkan kasus di mana kita melakukan panggilan rekursif dan menggunakan hasil dari panggilan rekursif untuk panggilan rekursif itu.
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));
}
Membuat iteratif panggilan rekursif pertama adalah mudah:
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));
}
Kami kemudian dapat membersihkan menghapus goto
untuk menangkal velociraptors dan bayangan 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;
}
Tetapi untuk menghapus panggilan rekursif lainnya, kita harus menyimpan nilai beberapa panggilan ke stack:
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;
}
Sekarang, ketika kita mempertimbangkan kode sumber, kita tentu mengubah metode rekursif kita menjadi yang berulang.
Mempertimbangkan apa yang telah dikompilasi, kami telah mengubah kode yang menggunakan panggilan stack untuk mengimplementasikan rekursi ke dalam kode yang tidak (dan dalam melakukannya mengubah kode yang akan membuang pengecualian stack-overflow bahkan untuk nilai yang sangat kecil ke dalam kode yang hanya akan butuh waktu yang sangat lama untuk kembali [lihat Bagaimana saya bisa mencegah fungsi Ackerman saya meluap tumpukan? untuk beberapa optimisasi lebih lanjut yang membuatnya benar-benar kembali untuk banyak input yang lebih mungkin]).
Mempertimbangkan bagaimana rekursi diimplementasikan secara umum, kami telah mengubah kode yang menggunakan panggilan-tumpukan menjadi kode yang menggunakan tumpukan yang berbeda untuk menahan operasi yang tertunda. Karena itu kita dapat berpendapat bahwa itu masih bersifat rekursif, ketika dipertimbangkan pada tingkat rendah itu.
Dan pada level itu, memang tidak ada cara lain di sekitarnya. Jadi jika Anda menganggap metode itu sebagai rekursif, maka memang ada hal-hal yang tidak dapat kita lakukan tanpanya. Meskipun secara umum kami tidak memberi label kode semacam itu sebagai rekursif. Istilah rekursi berguna karena mencakup serangkaian pendekatan tertentu dan memberi kita cara untuk membicarakannya, dan kita tidak lagi menggunakan salah satunya.
Tentu saja, semua ini mengasumsikan Anda punya pilihan. Ada kedua bahasa yang melarang panggilan rekursif, dan bahasa yang tidak memiliki struktur pengulangan yang diperlukan untuk iterasi.