Bisakah setiap rekursi diubah menjadi iterasi?

181

Sebuah benang reddit dibesarkan pertanyaan rupanya menarik:

Fungsi rekursif ekor dapat dengan mudah diubah menjadi fungsi berulang. Yang lain, dapat ditransformasikan dengan menggunakan tumpukan eksplisit. Bisakah setiap rekursi diubah menjadi iterasi?

Contoh (penghitung?) Dalam posting adalah pasangan:

(define (num-ways x y)
  (case ((= x 0) 1)
        ((= y 0) 1)
        (num-ways2 x y) ))

(define (num-ways2 x y)
  (+ (num-ways (- x 1) y)
     (num-ways x (- y 1))
Tordek
sumber
3
Saya tidak melihat bagaimana ini contohnya. Teknik tumpukan akan bekerja. Tidak akan cantik, dan saya tidak akan menulisnya, tetapi itu bisa dilakukan. Tampaknya akdas mengakui itu di tautan Anda.
Matthew Flaschen
(Num-ways xy) Anda hanya (x + y) choosex = (x + y)! / (X! Y!), Yang tidak memerlukan rekursi.
ShreevatsaR
3
Duplikat dari: stackoverflow.com/questions/531668
Henk Holterman
Saya akan mengatakan bahwa rekursi hanyalah kenyamanan.
e2-e4

Jawaban:

181

Bisakah Anda selalu mengubah fungsi rekursif menjadi fungsi berulang? Ya, tentu saja, dan tesis Gereja-Turing membuktikannya jika ingatanku bermanfaat. Dalam istilah awam, ini menyatakan bahwa apa yang dapat dihitung dengan fungsi rekursif dapat dihitung dengan model iteratif (seperti mesin Turing) dan sebaliknya. Tesis ini tidak memberi tahu Anda dengan tepat bagaimana melakukan konversi, tetapi ia mengatakan bahwa itu pasti mungkin.

Dalam banyak kasus, mengonversi fungsi rekursif itu mudah. Knuth menawarkan beberapa teknik dalam "The Art of Computer Programming". Dan seringkali, sesuatu yang dihitung secara rekursif dapat dihitung dengan pendekatan yang sama sekali berbeda dalam waktu dan ruang yang lebih sedikit. Contoh klasik dari ini adalah angka Fibonacci atau urutannya. Anda pasti telah menemui masalah ini dalam rencana gelar Anda.

Di sisi lain dari koin ini, kita tentu bisa membayangkan sistem pemrograman yang begitu canggih sehingga memperlakukan definisi rumus dari rekursif sebagai undangan untuk memoize hasil sebelumnya, sehingga menawarkan manfaat kecepatan tanpa kerumitan memberi tahu komputer secara tepat langkah mana yang harus dilakukan. ikuti perhitungan formula dengan definisi rekursif. Dijkstra hampir pasti membayangkan sistem seperti itu. Dia menghabiskan waktu lama mencoba memisahkan implementasi dari semantik bahasa pemrograman. Kemudian lagi, bahasa pemrograman non-deterministik dan multi-pemrosesan berada di liga di atas programmer profesional berlatih.

Dalam analisis akhir, banyak fungsi yang lebih mudah dimengerti, dibaca, dan ditulis dalam bentuk rekursif. Kecuali jika ada alasan kuat, Anda mungkin seharusnya tidak (secara manual) mengonversi fungsi-fungsi ini ke algoritma iteratif yang eksplisit. Komputer Anda akan menangani pekerjaan itu dengan benar.

Saya bisa melihat satu alasan kuat. Misalkan Anda memiliki sistem prototipe dalam bahasa tingkat super tinggi seperti Skema [ mengenakan pakaian asbes ], Lisp, Haskell, OCaml, Perl, atau Pascal. Misalkan kondisinya sedemikian rupa sehingga Anda memerlukan implementasi di C atau Java. (Mungkin itu politik.) Maka Anda tentu bisa memiliki beberapa fungsi yang ditulis secara rekursif tetapi yang, diterjemahkan secara harfiah, akan meledak sistem runtime Anda. Sebagai contoh, rekursi ekor tak terbatas dimungkinkan dalam Skema, tetapi idiom yang sama menyebabkan masalah bagi lingkungan C yang ada. Contoh lain adalah penggunaan fungsi bersarang secara leksikal dan ruang lingkup statis, yang didukung Pascal tetapi C tidak.

Dalam keadaan ini, Anda mungkin mencoba mengatasi perlawanan politik terhadap bahasa asli. Anda mungkin menemukan diri Anda menerapkan Lisp dengan buruk, seperti dalam hukum kesepuluh (lidah-di-pipi) Greenspun. Atau Anda mungkin hanya menemukan pendekatan yang sama sekali berbeda untuk solusi. Tapi bagaimanapun juga, pasti ada jalan.

Ian
sumber
10
Bukankah Gereja-Turing belum terbukti?
Liran Orevi
15
@eyelidlessness: Jika Anda dapat menerapkan A dalam B, itu berarti B memiliki setidaknya kekuatan sebanyak A. Jika Anda tidak dapat menjalankan beberapa pernyataan A di A-implementasi-of-B, maka itu bukan implementasi. Jika A dapat diimplementasikan dalam B dan B dapat diimplementasikan dalam A, daya (A)> = daya (B), dan daya (B)> = daya (A). Satu-satunya solusi adalah daya (A) == daya (B).
Tordek
6
re: paragraf 1: Anda berbicara tentang kesetaraan model perhitungan, bukan tesis Church-Turing. Kesetaraan itu AFAIR dibuktikan oleh Gereja dan / atau Turing, tetapi itu bukan tesis. Tesis ini adalah fakta eksperimental bahwa segala sesuatu yang dapat dihitung secara intuitif dapat dihitung dalam arti matematis yang ketat (oleh mesin Turing / fungsi rekursif dll.). Ini bisa dibantah jika menggunakan hukum fisika kita bisa membangun beberapa komputer nonkelas yang mengkomputasi sesuatu yang tidak dapat dilakukan mesin Turing (misalnya, menghentikan masalah). Sedangkan kesetaraan adalah teorema matematika, dan itu tidak akan dibantah.
sdcvvc
7
Bagaimana jawaban ini mendapat suara positif? Pertama itu menggabungkan kelengkapan Turing dengan tesis Gereja-Turing, kemudian membuat sekelompok handwaving yang salah, menyebutkan sistem "canggih" dan menjatuhkan rekursi ekor tak terbatas yang malas (yang dapat Anda lakukan dalam C atau bahasa lengkap Turing karena .. eh. apakah ada yang tahu apa artinya Turing yang lengkap?). Maka kesimpulan yang bisa diambil dengan tangan penuh harapan, seperti ini adalah pertanyaan tentang Oprah dan yang Anda butuhkan hanyalah menjadi positif dan meneguhkan? Jawaban yang mengerikan!
ex0du5
8
Dan bs tentang semantik ??? Betulkah? Ini adalah pertanyaan tentang transformasi sintaksis, dan entah bagaimana ini menjadi cara yang bagus untuk memberi nama drop Dijkstra dan menyiratkan Anda tahu sesuatu tentang pi-kalkulus? Izinkan saya memperjelas hal ini: apakah orang melihat semantik denotasional suatu bahasa atau model lain tidak akan berpengaruh pada jawaban atas pertanyaan ini. Apakah bahasa itu majelis atau bahasa pemodelan domain generatif tidak ada artinya. Ini hanya tentang Turing kelengkapan dan mentransformasikan "tumpukan variabel" menjadi "setumpuk variabel".
ex0du5
43

Apakah selalu mungkin untuk menulis formulir non-rekursif untuk setiap fungsi rekursif?

Iya. Sebuah bukti formal sederhana adalah untuk menunjukkan bahwa rekursi μ dan kalkulus non-rekursif seperti GOTO keduanya Turing lengkap. Karena semua kalkulus lengkap Turing sepenuhnya setara dalam kekuatan ekspresifnya, semua fungsi rekursif dapat diimplementasikan oleh kalkulus lengkap Turing-lengkap non-rekursif.

Sayangnya, saya tidak dapat menemukan definisi GOTO online yang bagus dan formal jadi berikut ini:

Program GOTO adalah urutan perintah P yang dijalankan pada a mesin register sehingga P adalah salah satu dari yang berikut:

  • HALT, yang menghentikan eksekusi
  • r = r + 1dimana rada register
  • r = r – 1dimana rada register
  • GOTO x dimana x label
  • IF r ≠ 0 GOTO x dimana r ada register dan xlabel
  • Label, diikuti oleh perintah di atas.

Namun, konversi antara fungsi rekursif dan non-rekursif tidak selalu sepele (kecuali dengan penerapan kembali tumpukan panggilan secara manual).

Untuk informasi lebih lanjut lihat jawaban ini .

Konrad Rudolph
sumber
Jawaban bagus! Namun dalam prakteknya saya mengalami kesulitan besar untuk mengambil algos rekursif menjadi iteratif. Sebagai contoh, saya sejauh ini tidak dapat mengubah typer monomorfik yang disajikan di sini community.topcoder.com/… menjadi sebuah algoritma iteratif
Nils
31

Rekursi diimplementasikan sebagai tumpukan atau konstruksi serupa dalam penerjemah atau kompiler yang sebenarnya. Jadi, Anda tentu dapat mengonversi fungsi rekursif ke mitra berulang karena itulah yang selalu dilakukan (jika secara otomatis) . Anda hanya akan menduplikasi pekerjaan kompiler dalam ad-hoc dan mungkin dengan cara yang sangat jelek dan tidak efisien.

Vinko Vrsalovic
sumber
13

Pada dasarnya ya, pada dasarnya apa yang Anda akhirnya harus lakukan adalah mengganti panggilan metode (yang secara implisit mendorong negara ke tumpukan) ke tumpukan eksplisit mendorong untuk mengingat di mana 'panggilan sebelumnya' telah mencapai, dan kemudian jalankan 'metode yang disebut' sebagai gantinya.

Saya akan membayangkan bahwa kombinasi loop, stack dan state-machine dapat digunakan untuk semua skenario dengan mensimulasikan pemanggilan metode. Apakah ini akan menjadi 'lebih baik' (baik lebih cepat, atau lebih efisien dalam beberapa hal) tidak benar-benar mungkin untuk dikatakan secara umum.

jerigen
sumber
9
  • Alur eksekusi fungsi rekursif dapat direpresentasikan sebagai pohon.

  • Logika yang sama dapat dilakukan oleh loop, yang menggunakan struktur data untuk melintasi pohon itu.

  • Traversal mendalam-pertama dapat dilakukan menggunakan tumpukan, luas traversal-pertama dapat dilakukan dengan menggunakan antrian.

Jadi jawabannya adalah ya. Mengapa: https://stackoverflow.com/a/531721/2128327 .

Bisakah rekursi dilakukan dalam satu loop? Ya karena

mesin Turing melakukan semua yang dilakukannya dengan menjalankan satu loop:

  1. ambil instruksi,
  2. mengevaluasi itu,
  3. kebagian 1.
Khaled.K
sumber
7

Ya, menggunakan tumpukan secara eksplisit (tapi rekursi jauh lebih menyenangkan untuk dibaca, IMHO).

dfa
sumber
17
Saya tidak akan mengatakan itu selalu lebih menyenangkan untuk dibaca. Baik iterasi dan rekursi memiliki tempat mereka.
Matthew Flaschen
6

Ya, selalu memungkinkan untuk menulis versi non-rekursif. Solusi sepele adalah dengan menggunakan struktur data stack dan mensimulasikan eksekusi rekursif.

Heinzi
sumber
Yang mana bisa mengalahkan tujuan jika struktur data stack Anda dialokasikan pada stack, atau memakan waktu lebih lama jika itu dialokasikan pada heap, bukan? Kedengarannya sepele tetapi tidak efisien bagi saya.
conradkleinespel
1
@conradk Dalam beberapa kasus, ini adalah hal praktis yang harus dilakukan jika Anda harus melakukan beberapa operasi rekursif pohon pada masalah yang cukup besar untuk menghabiskan panggilan stack; memori tumpukan biasanya jauh lebih banyak.
jamesdlin
4

Pada prinsipnya selalu mungkin untuk menghapus rekursi dan menggantinya dengan iterasi dalam bahasa yang memiliki status tak terbatas baik untuk struktur data maupun untuk tumpukan panggilan. Ini adalah konsekuensi dasar dari tesis Gereja-Turing.

Mengingat bahasa pemrograman yang sebenarnya, jawabannya tidak sejelas itu. Masalahnya adalah sangat mungkin untuk memiliki bahasa di mana jumlah memori yang dapat dialokasikan dalam program terbatas tetapi di mana jumlah panggilan stack yang dapat digunakan tidak terbatas (32-bit C di mana alamat variabel stack tidak dapat di akses). Dalam hal ini, rekursi lebih kuat hanya karena memiliki lebih banyak memori yang dapat digunakan; tidak ada memori yang dapat dialokasikan secara eksplisit untuk meniru tumpukan panggilan. Untuk diskusi terperinci tentang ini, lihat diskusi ini .

Zayenz
sumber
2

Semua fungsi yang dapat dihitung dapat dihitung dengan Mesin Turing dan karenanya sistem rekursif dan mesin Turing (sistem iteratif) adalah setara.

JOBBINE
sumber
1

Terkadang mengganti rekursi jauh lebih mudah dari itu. Rekursi dulunya adalah hal yang modis yang diajarkan di CS pada 1990-an, dan banyak pengembang pada waktu itu memperkirakan jika Anda memecahkan sesuatu dengan rekursi, itu adalah solusi yang lebih baik. Jadi mereka akan menggunakan rekursi alih-alih mengulang ke belakang untuk membalik urutan, atau hal-hal konyol seperti itu. Jadi, kadang-kadang menghilangkan rekursi adalah jenis latihan "duh, itu sudah jelas".

Ini kurang masalah sekarang, karena fashion telah bergeser ke teknologi lainnya.

Matthias Wandel
sumber
0

Menghapus rekursi adalah masalah yang kompleks dan layak dalam kondisi yang terdefinisi dengan baik.

Kasus-kasus di bawah ini adalah yang mudah:

Nick Dandoulakis
sumber
0

Appart dari tumpukan eksplisit, pola lain untuk mengubah rekursi menjadi iterasi adalah dengan penggunaan trampolin.

Di sini, fungsi mengembalikan hasil akhir, atau penutupan panggilan fungsi yang seharusnya dilakukan. Kemudian, fungsi inisiasi (trampolin) terus memohon penutupan yang dikembalikan sampai hasil akhir tercapai.

Pendekatan ini berfungsi untuk fungsi rekursif yang saling menguntungkan, tetapi saya khawatir ini hanya berfungsi untuk panggilan ekor.

http://en.wikipedia.org/wiki/Trampoline_(computers)

Chris Vest
sumber
0

Saya akan mengatakan ya - panggilan fungsi tidak lain adalah operasi goto dan stack (secara kasar). Yang perlu Anda lakukan adalah meniru tumpukan yang dibangun saat menjalankan fungsi dan melakukan sesuatu yang mirip dengan goto (Anda dapat meniru goto dengan bahasa yang juga tidak memiliki kata kunci ini secara eksplisit).

sfussenegger
sumber
1
Saya pikir OP sedang mencari bukti atau sesuatu yang substantif
Tim
0

Lihat entri berikut di wikipedia, Anda dapat menggunakannya sebagai titik awal untuk menemukan jawaban lengkap untuk pertanyaan Anda.

Mengikuti paragraf yang mungkin memberi Anda beberapa petunjuk tentang di mana untuk memulai:

Memecahkan hubungan perulangan berarti mendapatkan solusi bentuk-tertutup : fungsi non-rekursif dari n.

Lihat juga paragraf terakhir dari entri ini .

Alberto Zaccagni
sumber
-1

tazzego, rekursi berarti bahwa suatu fungsi akan memanggil dirinya sendiri apakah Anda suka atau tidak. Ketika orang berbicara tentang apakah sesuatu dapat dilakukan tanpa rekursi, mereka memaksudkan ini dan Anda tidak dapat mengatakan "tidak, itu tidak benar, karena saya tidak setuju dengan definisi rekursi" sebagai pernyataan yang valid.

Dengan mengingat hal itu, hampir semua yang Anda katakan tidak masuk akal. Satu-satunya hal lain yang Anda katakan bukan omong kosong adalah gagasan bahwa Anda tidak dapat membayangkan pemrograman tanpa callstack. Itu adalah sesuatu yang telah dilakukan selama beberapa dekade sampai menggunakan callstack menjadi populer. FORTRAN versi lama tidak memiliki callstack dan berfungsi dengan baik.

By the way, ada bahasa Turing-lengkap yang hanya menerapkan rekursi (misalnya SML) sebagai sarana perulangan. Ada juga bahasa Turing-complete yang hanya mengimplementasikan iterasi sebagai alat perulangan (misalnya FORTRAN IV). Tesis Church-Turing membuktikan bahwa segala sesuatu yang mungkin dalam bahasa rekursi hanya dapat dilakukan dalam bahasa non-rekursif dan vica-versa oleh fakta bahwa mereka berdua memiliki sifat kelengkapan turing.

Richard
sumber
-3

Berikut ini adalah algoritma berulang:

def howmany(x,y)
  a = {}
  for n in (0..x+y)
    for m in (0..n)
      a[[m,n-m]] = if m==0 or n-m==0 then 1 else a[[m-1,n-m]] + a[[m,n-m-1]] end
    end
  end
  return a[[x,y]]
end
Jules
sumber