Iterasi dapat menggantikan Rekursi?

42

Saya telah melihat seluruh stack Overflow, misalnya di sini , di sini , di sini , di sini , di sini dan beberapa yang lain saya tidak peduli untuk menyebutkan, bahwa "program apa pun yang menggunakan rekursi dapat dikonversi ke program yang hanya menggunakan iterasi".

Bahkan ada utas yang sangat tervvotasikan dengan jawaban yang sangat tervotifikasi yang mengatakan ya itu mungkin.

Sekarang saya tidak mengatakan mereka salah. Hanya saja jawaban itu melawan sedikit pengetahuan dan pemahaman saya tentang komputasi.

Saya percaya setiap fungsi berulang dapat dinyatakan sebagai rekursi, dan wikipedia memiliki pernyataan untuk efek itu. Namun, saya ragu bahwa yang terjadi adalah sebaliknya. Untuk satu, saya ragu fungsi rekursif non-primitif dapat diekspresikan secara iteratif.

Saya juga ragu operasi-hiper dapat diekspresikan secara iteratif.

Dalam jawabannya (yang saya tidak mengerti dengan cara) untuk pertanyaan saya @ YuvalFIlmus mengatakan bahwa tidak mungkin untuk mengubah urutan operasi matematika menjadi urutan penambahan.

Jika jawaban YF memang benar (saya kira begitu, tetapi alasannya berada di atas kepala saya), bukankah ini berarti bahwa tidak setiap rekursi dapat dikonversi menjadi iterasi? Karena jika mungkin untuk mengubah setiap rekursi menjadi iterasi, saya dapat mengekspresikan semua operasi sebagai urutan penambahan.

Pertanyaan saya adalah ini:

Bisakah setiap rekursi dikonversi menjadi iterasi dan mengapa?

Tolong beri jawaban, siswa sekolah menengah yang cerah atau sarjana tahun pertama akan mengerti. Terima kasih.

PS Saya tidak tahu apa itu primitif rekursif (saya tahu tentang fungsi Ackermann, dan itu bukan rekursif primitif, tetapi masih bisa dihitung. ALA pengetahuan saya tentang itu berasal dari halaman Wikipedia pada fungsi Ackermann.)

PPS: Jika jawabannya adalah ya, bisakah Anda misalnya menulis versi berulang dari fungsi non-primitif-rekursif. Misalnya Ackermann dalam jawabannya. Ini akan membantu saya mengerti.

Tobi Alafin
sumber
21
Segala sesuatu yang Anda jalankan pada CPU adalah berulang. Anda mungkin menulisnya secara rekursif dalam bahasa tingkat tinggi, tetapi itu tetap dikompilasi menjadi sekelompok instruksi assembler berulang. Jadi secara harfiah, kompiler melakukan persis apa yang Anda tanyakan, itu mengubah semua rekursi mewah Anda menjadi sekelompok instruksi berulang untuk CPU.
Davor
6
Pada level rendah, kebanyakan dari kita tahu bahwa rekursi sama dengan iterasi dan stack. Rekursi model tata bahasa bebas konteks, sedangkan automata pushdown adalah "program" berulang dengan memori tumpukan.
Hendrik Jan
2
Hanya menunjukkan bahwa pada umumnya ide yang baik untuk menunggu setidaknya 24 jam untuk melihat apakah jawaban lain muncul. Pertanyaan yang diterima tampaknya cukup panjang dan berbelit-belit bagi saya, terus terang, dan tampaknya dengan sengaja mempersulit masalah lebih dari yang diperlukan. Jawaban dasar untuk pertanyaan Anda adalah "tumpukan yang digunakan untuk rekursi dapat secara eksplisit dilaksanakan dengan cara yang berulang", dan tidak perlu jauh lebih rumit dari itu. Pertimbangan tentang memori menjadi tidak terbatas atau tidak ikut berperan karena fitur itu juga diperlukan untuk tumpukan rekursi.
AnoE
dimungkinkan untuk menerapkan emulator mesin Turing hanya dengan iterasi
Sarge Borsch
Dalam kelas bahasa komparatif yang saya ambil sejak lama, kami harus menulis fungsi Ackermann dalam perakitan, dalam FORTRAN (bukan Fortran modern), dan dalam bahasa rekursif pilihan seseorang. Jadi ya, itu mungkin dalam praktik. Dan tentu saja itu mungkin secara teori. Lihat, misalnya, pertanyaan membuktikan bahwa mesin Turing dan kalkulus lambda adalah setara .
David Hammen

Jawaban:

52

Dimungkinkan untuk mengganti rekursi dengan iterasi plus memori tanpa batas .

Jika Anda hanya memiliki iterasi (katakanlah, sementara loop) dan jumlah memori yang terbatas, maka semua yang Anda miliki adalah otomat terbatas. Dengan jumlah memori yang terbatas, perhitungan memiliki sejumlah langkah yang mungkin terbatas, jadi dimungkinkan untuk mensimulasikan semuanya dengan otomat terbatas.

Memori yang tidak terbatas mengubah kesepakatan. Memori tanpa batas ini dapat mengambil banyak bentuk yang ternyata memiliki kekuatan ekspresif yang setara. Sebagai contoh, mesin Turing membuatnya tetap sederhana: ada satu kaset, dan komputer hanya dapat bergerak maju atau mundur pada kaset dengan satu langkah pada satu waktu - tetapi itu cukup untuk melakukan apa pun yang dapat Anda lakukan dengan fungsi rekursif.

Mesin Turing dapat dilihat sebagai model komputer yang diidealkan (finite state machine) dengan beberapa penyimpanan ekstra yang tumbuh sesuai permintaan. Perhatikan bahwa sangat penting bahwa tidak hanya tidak ada batasan pada pita, tetapi bahkan dengan input, Anda tidak dapat memprediksi dengan pasti berapa banyak pita yang akan dibutuhkan. Jika Anda dapat memprediksi (yaitu menghitung) berapa banyak pita yang dibutuhkan dari input, maka Anda dapat memutuskan apakah perhitungan akan berhenti dengan menghitung ukuran pita maksimum dan kemudian memperlakukan seluruh sistem, termasuk pita yang sekarang terbatas, sebagai mesin keadaan terbatas. .

Cara lain untuk mensimulasikan mesin Turing dengan komputer adalah sebagai berikut. Simulasikan mesin Turing dengan program komputer yang menyimpan awal kaset dalam memori. Jika perhitungan mencapai akhir bagian dari pita yang sesuai dengan memori, gantilah komputer dengan komputer yang lebih besar dan jalankan kembali perhitungan.

Sekarang anggaplah Anda ingin mensimulasikan perhitungan rekursif dengan komputer. Teknik-teknik untuk mengeksekusi fungsi rekursif sudah terkenal: setiap pemanggilan fungsi memiliki memori, yang disebut stack frame . Yang terpenting, fungsi rekursif dapat menyebarkan informasi melalui beberapa panggilan dengan mengirimkan variabel ke sekitarnya. Dalam hal implementasi pada komputer, itu berarti bahwa panggilan fungsi dapat mengakses frame stack panggilan induk (grand-) * .

Komputer adalah prosesor - mesin keadaan terbatas (dengan sejumlah besar keadaan, tapi kami sedang melakukan teori komputasi di sini, jadi yang penting adalah bahwa itu terbatas) - ditambah dengan memori terbatas. Mikroprosesor menjalankan satu raksasa sambil memutar: "saat daya menyala, baca instruksi dari memori dan jalankan". (Prosesor nyata jauh lebih kompleks dari itu, tetapi tidak mempengaruhi apa yang dapat mereka hitung, hanya seberapa cepat dan nyaman mereka melakukannya.) Komputer dapat menjalankan fungsi rekursif dengan hanya loop sementara ini untuk memberikan iterasi, ditambah mekanisme untuk mengakses memori, termasuk kemampuan untuk meningkatkan ukuran memori sesuka hati.

Jika Anda membatasi rekursi menjadi rekursi primitif, maka Anda dapat membatasi iterasi hingga iterasi terbatas . Artinya, alih-alih menggunakan loop sementara dengan waktu berjalan yang tidak dapat diprediksi, Anda dapat menggunakan untuk loop di mana jumlah iterasi diketahui di awal loop¹. Jumlah iterasi mungkin tidak diketahui pada awal program: iterasinya sendiri dapat dihitung dengan loop sebelumnya.

Saya bahkan tidak akan membuat sketsa bukti di sini, tetapi ada hubungan intuitif antara beralih dari rekursi primitif ke rekursi penuh, dan beralih dari loop ke loop sementara: dalam kedua kasus, itu melibatkan tidak mengetahui sebelumnya kapan Anda akan berhenti. Dengan rekursi penuh, ini dilakukan dengan operator minimisasi, di mana Anda terus berjalan sampai Anda menemukan parameter yang memenuhi syarat. Dengan while loop, ini dilakukan dengan terus berjalan sampai kondisi loop terpenuhi.

¹ loop dalam bahasa mirip C dapat melakukan iterasi tanpa batas sama seperti , hanya masalah konvensi untuk membatasi mereka untuk iterasi terbatas. Ketika orang berbicara tentang "untuk loop" dalam teori perhitungan, itu berarti hanya loop yang dihitung dari 1 ke n (atau setara). forwhilen

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Menerima penjelasan terperinci, disimpan di tingkat yang diminta.
Tobi Alafin
12
Saya pikir itu layak dicatat daripada di komputer nyata, rekursi juga menggunakan memori (menumpuk panggilan). Jadi dalam aplikasi praktis, tidak ada persyaratan memori tidak terikat (karena semuanya terikat olehnya secara merata). Dan rekursi seringkali membutuhkan lebih banyak memori daripada iterasi.
Agent_L
@ Agg_L Ya, rekursi membutuhkan memori tidak terbatas, untuk menyimpan semua frame stack. Dengan pendekatan rekursi, ada persyaratan memori tidak terbatas, tetapi tidak secara intuitif dapat dipisahkan dari urutan operasi seperti dengan iterasi.
Gilles 'SANGAT berhenti menjadi jahat'
1
Mungkin yang menarik adalah tesis Gereja-Turing. Mesin Turing adalah mesin yang sangat iteratif tanpa konsep rekursi (inheren). Kalkulus Lambda adalah bahasa yang dirancang untuk mengekspresikan perhitungan secara rekursif. Tesis Gereja-Turing menunjukkan bahwa semua ekspresi lambda dapat dievaluasi pada mesin Turing, yang menghubungkan rekursi dan iterasi.
Cort Ammon
1
@BlackVegetable Jika metode rekursif tidak memiliki variabel internal, dan satu-satunya memori yang digunakan adalah panggilan stack (apa yang dapat dioptimalkan oleh TCO), maka versi iteratif kemungkinan besar juga tidak memiliki variabel internal dan hanya menggunakan jumlah memori yang konstan ( variabel) dibagi di semua iterasi, seperti penghitung.
Agent_L
33

Setiap rekursi dapat dikonversi menjadi iterasi, seperti yang disaksikan oleh CPU Anda, yang mengeksekusi program arbitrer menggunakan iterasi tak terbatas mengambil-eksekusi. Ini adalah bentuk teorema Böhm-Jacopini . Selain itu, banyak model komputasi Turing-lengkap tidak memiliki rekursi, misalnya mesin Turing dan mesin pencacah .

Fungsi rekursif primitif sesuai dengan program yang menggunakan iterasi terbatas , yaitu, Anda harus menentukan jumlah iterasi yang dijalankan loop terlebih dahulu. Iterasi terikat tidak dapat mensimulasikan rekursi secara umum, karena fungsi Ackermann bukan rekursif primitif. Tetapi iterasi tanpa batas dapat mensimulasikan fungsi yang dapat dihitung sebagian.

Yuval Filmus
sumber
25

a(n,m)

s

push(s,x)xxpop(s)empty(s)

Ackermann(n0,m0):

  • s= (inisialisasi tumpukan kosong)
  • push(s,n0)
  • push(s,m0)
  • while(true):
    • mpop(s)
    • if(empty(s)):
      • return m (ini mengakhiri loop)
    • npop(s)
    • if(n=0):
      • push(s,m+1)
    • else if(m=0):
      • push(s,n1)
      • push(s,1)
    • else:
      • push(s,n1)
      • push(s,n)
      • push(s,m1)

Saya menerapkan ini di Ceylon, Anda dapat menjalankannya di WebIDE , jika Anda mau. (Ini menampilkan tumpukan pada awal setiap iterasi dari loop.)

Tentu saja, ini hanya memindahkan stack panggilan implisit dari rekursi ke stack eksplisit dengan parameter dan mengembalikan nilai.

Paŭlo Ebermann
sumber
15
Saya pikir ini adalah poin penting. Anda telah menunjukkan secara eksplisit bahwa rekursi tidak ada yang istimewa dan dapat dihapus hanya dengan melacak panggilan yang ditumpuk sendiri, daripada membiarkan kompiler melakukannya. Rekursi hanyalah fitur kompiler yang membuat penulisan program semacam ini lebih mudah.
David Richerby
4
Terima kasih telah berupaya untuk menulis program yang diminta.
Tobi Alafin
16

Sudah ada beberapa jawaban bagus (yang bahkan tidak bisa saya harapkan untuk bersaing), tetapi saya ingin menyampaikan penjelasan sederhana ini.

Rekursi hanyalah manipulasi tumpukan runtime. Berulang menambahkan bingkai tumpukan baru (untuk permintaan baru fungsi rekursif), dan kembali menghapus bingkai tumpukan (untuk inovasi fungsi rekursif yang baru saja selesai). Rekursi akan menyebabkan sejumlah bingkai tumpukan ditambahkan / dihapus, sampai akhirnya semuanya keluar (semoga!) Dan hasilnya dikembalikan ke pemanggil.

Sekarang, apa yang akan terjadi jika Anda membuat stack Anda sendiri, mengganti panggilan fungsi rekursif dengan mendorong ke stack, diganti kembali dari fungsi rekursif dengan muncul stack, dan memiliki loop sementara yang berjalan sampai stack kosong?

Alexander
sumber
2

Sejauh yang saya tahu, dan dalam pengalaman saya sendiri, Anda dapat menerapkan rekursi apa pun sebagai iterasi. Seperti disebutkan di atas, rekursi menggunakan stack, yang secara konsep tidak terbatas, tetapi praktis terbatas (pernahkah Anda mendapatkan pesan stack overflow?). Pada hari-hari awal saya pemrograman (pada kuartal ketiga abad terakhir dari milenium terakhir) saya menggunakan bahasa non-rekursif menerapkan algoritma rekursif dan tidak punya masalah. Saya tidak yakin bagaimana orang akan membuktikannya.

Louis Giokas
sumber