Bisakah fungsi rekursif memiliki iterasi / loop?

12

Saya telah mempelajari tentang fungsi rekursif, dan tampaknya, mereka adalah fungsi yang menyebut diri mereka sendiri, dan tidak menggunakan iterasi / loop (jika tidak itu tidak akan menjadi fungsi rekursif).

Namun, saat menjelajahi web sebagai contoh (masalah 8-ratu-rekursif), saya menemukan fungsi ini:

private boolean placeQueen(int rows, int queens, int n) {
    boolean result = false;
    if (row < n) {
        while ((queens[row] < n - 1) && !result) {
            queens[row]++;
            if (verify(row,queens,n)) {
                ok = placeQueen(row + 1,queens,n);
            }
        }
        if (!result) {
            queens[row] = -1;
        }
    }else{
        result = true;
    }
    return result;
}

Ada whileloop yang terlibat.

... jadi saya agak bingung sekarang. Bisakah saya menggunakan loop atau tidak?

Akhir
sumber
5
Apakah itu dikompilasi. Iya. Jadi mengapa bertanya?
Thomas Eding
6
The seluruh definisi rekursi adalah bahwa di beberapa titik, fungsi mungkin re-disebut sebagai bagian dari eksekusi sendiri sebelum kembali (apakah itu re-disebut dengan sendirinya atau dengan beberapa fungsi lain itu panggilan). Tidak ada definisi yang mengecualikan kemungkinan perulangan.
cao
Sebagai tambahan untuk komentar cao, fungsi rekursif akan dipanggil kembali pada versi yang lebih mudah dari dirinya sendiri (jika tidak, itu akan berulang selamanya). Mengutip orbling (dari Dalam Bahasa Inggris, apa itu rekursi? ): "Pemrograman rekursif adalah proses mengurangi masalah secara progresif untuk lebih mudah menyelesaikan versi-versi itu sendiri." Dalam hal ini, versi tersulit placeQueenadalah "place 8 queens" dan versi yang lebih mudah placeQueenadalah "place 7 queens" (lalu place 6, dll.)
Brian
Anda bisa menggunakan apa saja yang berfungsi Omega. Sangat jarang spesifikasi perangkat lunak menentukan gaya pemrograman apa yang digunakan - kecuali Anda di sekolah dan tugas Anda mengatakannya.
Apoorv Khurasia
@ThomasEding: Ya, tentu saja kompilasi dan bekerja. Tapi saya hanya belajar teknik saat ini - Yang penting bagi saya, pada titik ini, adalah konsep / definisi yang ketat dan bukan cara programmer menggunakannya saat ini. Jadi saya bertanya apakah konsep yang saya miliki benar (yang tidak, tampaknya).
Omega

Jawaban:

41

Anda salah memahami rekursi: meskipun dapat digunakan untuk menggantikan iterasi, sama sekali tidak ada persyaratan untuk fungsi rekursif untuk tidak memiliki iterasi internal pada dirinya sendiri.

Satu-satunya persyaratan agar suatu fungsi dianggap rekursif adalah keberadaan jalur kode yang digunakan untuk memanggil dirinya, secara langsung atau tidak langsung. Semua fungsi rekursif yang benar juga memiliki semacam kondisional, mencegahnya dari "berulang" selamanya.

Fungsi rekursif Anda sangat ideal untuk menggambarkan struktur pencarian rekursif dengan pengulangan. Dimulai dengan memeriksa kondisi keluar row < n, dan mulai membuat keputusan pencarian pada tingkat rekursi (yaitu memilih posisi yang memungkinkan untuk nomor ratu row). Setelah setiap iterasi, panggilan rekursif dibuat untuk membangun konfigurasi yang telah ditemukan fungsi sejauh ini; akhirnya, "turun" ketika rowmencapai npanggilan rekursif yang ntingkatnya dalam.

dasblinkenlight
sumber
1
+1 untuk fungsi rekursif "benar" memiliki persyaratan, banyak yang salah di luar sana yang tidak heh
Jimmy Hoffa
6
+1 "berulang-ulang" selamanya `Turtle () {Turtle ();}
Mr.Mindor
1
@ Mr.Mindor Saya suka kutipan "Ini kura-kura sepanjang jalan" :)
dasblinkenlight
Itu membuat saya tersenyum :-)
Martijn Verburg
2
"Semua fungsi rekursif yang benar juga memiliki semacam kondisional, mencegahnya dari" berulang "selamanya." tidak benar dengan evaluasi yang tidak ketat.
Pubby
12

Struktur umum dari fungsi rekursif adalah sesuatu seperti ini:

myRecursiveFunction(inputValue)
begin
   if evaluateBaseCaseCondition(inputValue)=true then
       return baseCaseValue;
   else
       /*
       Recursive processing
       */
       recursiveResult = myRecursiveFunction(nextRecursiveValue); //nextRecursiveValue could be as simple as inputValue-1
       return recursiveResult;
   end if
end

Teks yang saya tandai /*recursive processing*/bisa berupa apa saja. Itu bisa termasuk perulangan, jika masalah yang diselesaikan memerlukannya, dan juga bisa mencakup panggilan rekursif ke myRecursiveFunction.

FrustratedWithFormsDesigner
sumber
1
Itu menyesatkan, karena itu menyiratkan bahwa hanya ada satu panggilan rekursif, dan cukup banyak mengecualikan kasus-kasus di mana panggilan rekursif itu sendiri di dalam satu lingkaran (misalnya B-tree traversal).
Peter Taylor
@PeterTaylor: Ya, saya berusaha membuatnya tetap sederhana.
FrustratedWithFormsDesigner
Atau bahkan beberapa panggilan tanpa loop, seperti melintasi pohon biner biasa, di mana Anda akan memiliki 2 panggilan karena setiap node memiliki 2 anak.
Izkata
6

Anda pasti dapat menggunakan loop dalam fungsi rekursif. Apa yang membuat suatu fungsi rekursif hanyalah fakta bahwa fungsi tersebut memanggil dirinya sendiri pada suatu titik di jalur eksekusi. Namun Anda harus memiliki beberapa kondisi untuk mencegah panggilan rekursi tak terbatas dari mana fungsi Anda tidak dapat kembali.

marco-fiset
sumber
1

Panggilan dan loop rekursif hanyalah dua cara / konstruk untuk mengimplementasikan komputasi berulang.

Sebuah whileloop berhubungan dengan panggilan berulang -ekor (lihat misalnya di sini ), yaitu iterasi di mana Anda tidak perlu menyimpan hasil antara antara dua iterasi (semua hasil dari satu siklus siap ketika Anda memasuki siklus berikutnya). Jika Anda perlu menyimpan hasil antara yang dapat Anda gunakan lagi nanti Anda bisa menggunakan satu whilelingkaran bersama dengan tumpukan (lihat di sini ), atau panggilan rekursif non-ekor-rekursif (yaitu sewenang-wenang).

Banyak bahasa memungkinkan Anda untuk menggunakan kedua mekanisme tersebut dan Anda dapat memilih salah satu yang lebih cocok untuk Anda dan bahkan menggabungkan keduanya dalam kode Anda. Dalam bahasa imperatif seperti C, C ++, Java, dll. Anda biasanya menggunakan a whileatau forloop ketika Anda tidak membutuhkan stack, dan Anda menggunakan panggilan rekursif ketika Anda membutuhkan stack (Anda secara implisit menggunakan run-time stack). Haskell (bahasa fungsional) tidak menawarkan struktur kontrol iterasi sehingga Anda hanya dapat menggunakan panggilan rekursif untuk melakukan iterasi.

Dalam contoh Anda (lihat komentar saya):

// queens should have type int [] , not int.
private boolean placeQueen(int row, int [] queens, int n)
{
    boolean result = false;
    if (row < n)
    {
        // Iterate with queens[row] = 1 to n - 1.
        // After each iteration, you either have a result
        // in queens, or you have to try the next column for
        // the current row: no intermediate result.
        while ((queens[row] < n - 1) && !result)
        {
            queens[row]++;
            if (verify(row,queens,n))
            {
                // I think you have 'result' here, not 'ok'.
                // This is another loop (iterate on row).
                // The loop is implemented as a recursive call
                // and the previous values of row are stored on
                // the stack so that we can resume with the previous
                // value if the current attempt finds no solution.
                result = placeQueen(row + 1,queens,n);
            }
        }
        if (!result) {
            queens[row] = -1;
        }
    }else{
        result = true;
    }
    return result;
}
Giorgio
sumber
1

Anda benar berpikir ada hubungan antara rekursi dan iterasi atau perulangan. Algoritma rekursif sering secara manual atau bahkan secara otomatis dikonversi ke solusi berulang menggunakan optimasi panggilan ekor.

Dalam delapan ratu, bagian rekursif terkait dengan menyimpan data yang diperlukan untuk pelacakan kembali. Ketika Anda memikirkan rekursi, penting untuk memikirkan apa yang didorong pada tumpukan. Tumpukan dapat berisi pass by parameter nilai dan variabel lokal yang memainkan peran kunci dalam algoritma, atau kadang-kadang hal-hal yang tidak begitu relevan seperti alamat pengirim atau dalam kasus ini, nilai yang diteruskan dengan jumlah ratu yang digunakan tetapi tidak diubah oleh algoritma.

Tindakan yang terjadi dalam delapan ratu adalah bahwa pada dasarnya kita diberikan solusi parsial untuk beberapa jumlah ratu dalam beberapa kolom pertama dari mana kita secara iteratif menentukan pilihan yang valid sejauh ini di kolom saat ini yang kami lewati secara rekursif untuk dievaluasi untuk kolom yang tersisa. Secara lokal, delapan ratu melacak baris apa yang dicoba dan jika pelacakan kembali terjadi, ia siap untuk melangkah melalui baris yang tersisa atau untuk melacak kembali dengan hanya mengembalikan jika tidak menemukan baris lain yang dapat bekerja.

Pengembang Don
sumber
0

Bagian "buat versi yang lebih kecil dari masalah" dapat memiliki loop. Selama metode menyebut dirinya lewat sebagai parameter versi lebih kecil dari masalah, metode ini bersifat rekursif. Tentu saja kondisi keluar, ketika versi terkecil dari masalah diselesaikan dan metode mengembalikan nilai, harus disediakan untuk menghindari kondisi stack overflow.

Metode dalam pertanyaan Anda bersifat rekursif.

Tulains Córdova
sumber
0

Rekursi pada dasarnya memanggil fungsi Anda lagi dan keuntungan utama rekursi adalah menghemat memori. Rekursi dapat memiliki loop di dalamnya mereka digunakan untuk melakukan beberapa operasi lain.

Akshay
sumber
Mohon berbeda. Banyak algoritma dapat bersifat rekursif atau berulang dan solusi rekursif seringkali jauh lebih intensif memori jika Anda menghitung alamat pengirim, parameter, dan variabel lokal yang harus didorong pada stack. Beberapa bahasa mendeteksi dan membantu mengoptimalkan rekursi ekor atau optimisasi panggilan ekor, tetapi ini terkadang bahasa atau kode tertentu.
PengembangDon