Memahami cara kerja fungsi rekursif

115

Seperti yang dijelaskan judulnya, saya memiliki pertanyaan pemrograman yang sangat mendasar yang belum bisa saya bahas. Menyaring semua (sangat pintar) "Untuk memahami rekursi, Anda harus terlebih dahulu memahami rekursi." balasan dari berbagai utas online saya masih belum cukup mengerti.

Memahami bahwa ketika dihadapkan dengan ketidaktahuan tentang apa yang tidak kita ketahui, kita cenderung mengajukan pertanyaan yang salah atau mengajukan pertanyaan yang benar secara tidak benar. Saya akan membagikan apa yang saya "pikirkan" pertanyaan saya dengan harapan seseorang dengan pandangan yang sama dapat berbagi beberapa sedikit pengetahuan yang akan membantu menyalakan bola lampu rekursif untuk saya!

Berikut fungsinya (sintaksnya ditulis di Swift):

func sumInts(a: Int, b: Int) -> Int {
    if (a > b) {
        return 0
    } else {
        return a + sumInts(a: a + 1, b: b)
    }
}

Kami akan menggunakan 2 dan 5 sebagai argumen kami:

println(sumInts(a: 2, b: 5))

Jelas jawabannya adalah 14. Tapi saya tidak mengerti bagaimana nilai itu dicapai.

Ini adalah 2 hangup saya:

  1. Fungsi tersebut dipanggil secara rekursif hingga suatu kondisi terpenuhi. Kondisi itu adalah a> b. Ketika kondisi ini terpenuhi, kembalikan 0. Pada pandangan pertama, saya mengharapkan nilai kembali menjadi 0 yang jelas salah.

  2. Mencetak nilai 'a' pada setiap iterasi menghasilkan nilai yang saya harapkan: 2, 3, 4, 5 (di mana titik 5 + 1> b yang memenuhi syarat pertama: a> b) tetapi saya tetap tidak ' t melihat bagaimana nilai 14 dicapai.

Pikiran pertama saya adalah bahwa sesuatu yang mirip dengan berikut ini terjadi secara ajaib:

var answer = a;
answer += a+1 until a > b;
return answer;   

Jadi mengesampingkan sihir, saya hanya tidak mendapatkan sesuatu. Saya ingin sekali memahami apa yang terjadi lebih dari sekadar secara implisit.

Jika seseorang dapat dengan ramah menjelaskan apa yang secara teknis terjadi selama fungsi semacam ini dan mengapa hasilnya tidak 0 dan bagaimana, pada akhirnya a + sumInts(a: a + 1, b: b) = 14, saya akan selamanya berhutang budi.

Jason Elwood
sumber
7
Rekursi adalah salah satu konsep pemrograman yang lebih mudah dipahami dalam istilah matematika daripada dalam kode; ada definisi yang bagus di sini
blgt
5
LearnYouARecursion, set soal lengkap dari profesor kelas dunia!
recursion.ninja
15
Saya hanya perlu mendorong Anda untuk mengetik "Rekursi" di kotak pencarian Google. Salah satu telur Paskah itu. Saya tidak akan merusak kejutan untuk Anda.
Floris
7
Kemungkinan duplikat stackoverflow.com/questions/25676961/…
Neil McGuigan

Jawaban:

107

Saya pikir kebingungan ini berasal dari anggapan bahwa "fungsi yang sama" dipanggil berkali-kali. Jika Anda menganggapnya sebagai "banyak salinan dari fungsi yang sama yang dipanggil", mungkin akan lebih jelas:

Hanya satu salinan fungsi yang pernah mengembalikan 0, dan ini bukan yang pertama (ini yang terakhir). Jadi hasil pemanggilan yang pertama bukan 0.

Untuk kebingungan kedua, saya pikir akan lebih mudah mengeja rekursi dalam bahasa Inggris. Baca baris ini:

return a + sumInts(a + 1, b: b)

sebagai "kembalikan nilai 'a' plus (nilai kembalian dari salinan lain dari fungsi tersebut, yang merupakan nilai salinan 'a' plus (nilai kembalian dari salinan lain dari fungsi tersebut, yang merupakan nilai salinan kedua dari ' a 'plus (... ", dengan setiap salinan dari fungsi tersebut menghasilkan salinan baru dari dirinya sendiri dengan peningkatan sebesar 1, hingga kondisi a> b terpenuhi.

Pada saat Anda mencapai kondisi a> b menjadi true, Anda memiliki tumpukan salinan fungsi yang (berpotensi secara sewenang-wenang) yang panjang, semuanya sedang dijalankan, semua menunggu hasil salinan berikutnya untuk mencari tahu apa yang mereka lakukan. harus menambahkan 'a'.

(edit: juga, sesuatu yang harus diperhatikan adalah bahwa tumpukan salinan dari fungsi yang saya sebutkan adalah hal nyata yang memakan memori nyata, dan akan merusak program Anda jika terlalu besar. Kompiler dapat mengoptimalkannya di beberapa kasus, tetapi ruang tumpukan yang melelahkan adalah batasan fungsi rekursif yang signifikan dan tidak menguntungkan dalam banyak bahasa)

Catfish_Man
sumber
7
Catfish_Man: Saya pikir Anda berhasil! Memikirkannya sebagai beberapa "salinan" dari fungsi yang sama sangat masuk akal. Aku masih membungkus kepalaku, tapi kupikir kau telah mengirimku ke jalan yang benar! Terima kasih telah meluangkan waktu dari hari sibuk Anda untuk membantu sesama pemrogram! Saya akan menandai jawaban Anda sebagai jawaban yang benar. Semoga hari mu menyenangkan!
Jason Elwood
13
Ini adalah analogi yang bagus - meskipun berhati-hatilah untuk tidak menganggapnya terlalu harfiah karena setiap "salinan" sebenarnya adalah kode yang sama persis. Yang berbeda untuk setiap salinan adalah semua data yang dikerjakannya.
Tim B
2
Saya tidak terlalu senang dengan menganggapnya sebagai salinan. Saya menemukan bahwa penjelasan yang lebih intuitif adalah untuk membedakan fungsi itu sendiri (kode, apa yang dilakukannya) dan pemanggilan fungsi (Instansiasi fungsi itu) yang terkait dengan konteks bingkai / eksekusi tumpukan. Fungsi tidak memiliki variabel lokalnya, mereka dibuat instance-nya saat fungsi dipanggil (dipanggil). Tapi saya kira ini akan dilakukan sebagai pengantar rekursi
Thomas
5
Istilah yang benar adalah bahwa ada beberapa pemanggilan fungsi. Setiap pemanggilan memiliki contoh variabel adan b.
Theodore Norvell
6
Ya, ada sejumlah besar ketelitian yang dapat ditambahkan ke jawaban ini. Saya sengaja mengabaikan perbedaan antara "contoh fungsi" dan "catatan aktivasi pemanggilan fungsi tunggal", karena itu adalah beban konseptual tambahan yang tidak benar-benar membantu dalam memahami masalah. Ini membantu dalam memahami masalah lain , jadi ini masih info berguna, di tempat lain. Komentar ini sepertinya tempat yang bagus untuk itu :)
Catfish_Man
130

1. Fungsi tersebut dipanggil secara rekursif hingga suatu kondisi terpenuhi. Kondisi itu a > b. Ketika kondisi ini terpenuhi, kembalikan 0. Pada pandangan pertama, saya mengharapkan nilai kembali menjadi 0 yang jelas salah.

Inilah yang akan dipikirkan oleh komputasi komputer sumInts(2,5)jika mampu:

I want to compute sumInts(2, 5)
for this, I need to compute sumInts(3, 5)
and add 2 to the result.
  I want to compute sumInts(3, 5)
  for this, I need to compute sumInts(4, 5)
  and add 3 to the result.
    I want to compute sumInts(4, 5)
    for this, I need to compute sumInts(5, 5)
    and add 4 to the result.
      I want to compute sumInts(5, 5)
      for this, I need to compute sumInts(6, 5)
      and add 5 to the result.
        I want to compute sumInts(6, 5)
        since 6 > 5, this is zero.
      The computation yielded 0, therefore I shall return 5 = 5 + 0.
    The computation yielded 5, therefore I shall return 9 = 4 + 5.
  The computation yielded 9, therefore I shall return 12 = 3 + 9.
The computation yielded 12, therefore I shall return 14 = 2 + 12.

Seperti yang Anda lihat, beberapa panggilan ke fungsi sumIntssebenarnya mengembalikan 0 namun ini bukan nilai akhir karena komputer masih harus menambahkan 5 ke 0 itu, lalu 4 ke hasilnya, lalu 3, lalu 2, seperti yang dijelaskan oleh empat kalimat terakhir dari pikiran komputer kita. Perhatikan bahwa dalam rekursi, komputer tidak hanya harus menghitung panggilan rekursif, tetapi juga harus mengingat apa yang harus dilakukan dengan nilai yang dikembalikan oleh panggilan rekursif. Ada area khusus dalam memori komputer yang disebut tumpukan di mana jenis informasi ini disimpan, ruang ini terbatas dan fungsi yang terlalu rekursif dapat menghabiskan tumpukan: ini adalah luapan tumpukan yang memberikan namanya ke situs web favorit kami.

Pernyataan Anda tampaknya membuat asumsi implisit bahwa komputer melupakan apa yang terjadi saat melakukan panggilan rekursif, tetapi ternyata tidak, inilah mengapa kesimpulan Anda tidak sesuai dengan pengamatan Anda.

2. Mencetak nilai 'a' pada setiap iterasi menghasilkan nilai yang saya harapkan: 2, 3, 4, 5 (di mana titik 5 + 1> b yang memenuhi syarat pertama: a> b) tetapi saya tetap tidak melihat bagaimana nilai 14 dicapai.

Ini karena nilai yang dikembalikan bukan adirinya sendiri tetapi jumlah dari nilai adan nilai yang dikembalikan oleh panggilan rekursif.

Michael Le Barbier Grünewald
sumber
3
Terima kasih telah meluangkan waktu untuk menulis jawaban yang luar biasa ini Michael! +1!
Jason Elwood
9
@JasonElwood Mungkin akan sangat membantu jika Anda memodifikasi sumIntssehingga benar-benar menuliskan “pengalaman komputer”. Setelah Anda menulis satu tangan dari fungsi-fungsi tersebut, Anda mungkin sudah "mengerti"!
Michael Le Barbier Grünewald
4
Ini adalah jawaban yang bagus, meskipun saya perhatikan bahwa tidak ada persyaratan bahwa aktivasi fungsi berlangsung pada struktur data yang disebut "tumpukan". Rekursi dapat diimplementasikan dengan gaya penerusan lanjutan, dalam hal ini tidak ada tumpukan sama sekali. Tumpukan hanyalah satu - sangat efisien, dan karena itu dalam penggunaan umum - reifikasi gagasan kelanjutan.
Eric Lippert
1
@EricLippert Sementara teknik yang digunakan untuk mengimplementasikan recursivity merupakan topik yang menarik per se , saya tidak yakin apakah itu akan berguna untuk OP-yang ingin memahami “cara kerjanya” -untuk terkena berbagai mekanisme digunakan. Sementara kelanjutan lewat gaya atau bahasa berdasarkan ekspansi (misalnya TeX dan m4) tidak secara intrinsik lebih sulit daripada paradigma pemrograman yang lebih umum, saya tidak akan tersinggung siapa pun dengan label ini “eksotis” dan sedikit kebohongan putih seperti “itu selalu terjadi pada satu tumpukan” harus membantu OP untuk memahami konsep tersebut. (Dan semacam tumpukan selalu terlibat.)
Michael Le Barbier Grünewald
1
Harus ada beberapa cara bagi perangkat lunak untuk mengingat apa yang dilakukannya, memanggil fungsi secara rekursif, dan kemudian kembali ke keadaan semula ketika kembali. Mekanisme ini bertindak seperti tumpukan, jadi lebih mudah untuk menyebutnya tumpukan, meskipun beberapa struktur data lain digunakan.
Barmar
48

Untuk memahami rekursi, Anda harus memikirkan masalahnya dengan cara yang berbeda. Alih-alih urutan logis besar langkah-langkah yang masuk akal secara keseluruhan, Anda malah mengambil masalah besar dan memecah menjadi masalah yang lebih kecil dan menyelesaikannya, setelah Anda memiliki jawaban untuk sub masalah Anda menggabungkan hasil dari sub masalah untuk membuat solusi untuk masalah yang lebih besar. Pikirkan Anda dan teman Anda perlu menghitung jumlah kelereng dalam ember besar. Anda masing-masing mengambil ember yang lebih kecil dan menghitungnya satu per satu dan setelah selesai Anda menjumlahkan totalnya .. Nah sekarang jika masing-masing dari Anda menemukan beberapa teman dan membagi ember lebih jauh, maka Anda hanya perlu menunggu teman-teman yang lain ini untuk cari tahu totalnya, berikan kembali kepada Anda masing-masing, Anda menambahkannya. Dan seterusnya.

Anda harus ingat setiap kali fungsi memanggil dirinya sendiri secara rekursif, ia membuat konteks baru dengan subset masalah, setelah bagian tersebut diselesaikan, ia akan dikembalikan sehingga iterasi sebelumnya dapat diselesaikan.

Mari saya tunjukkan langkah-langkahnya:

sumInts(a: 2, b: 5) will return: 2 + sumInts(a: 3, b: 5)
sumInts(a: 3, b: 5) will return: 3 + sumInts(a: 4, b: 5)
sumInts(a: 4, b: 5) will return: 4 + sumInts(a: 5, b: 5)
sumInts(a: 5, b: 5) will return: 5 + sumInts(a: 6, b: 5)
sumInts(a: 6, b: 5) will return: 0

setelah penjumlahan (a: 6, b: 5) telah dieksekusi, hasilnya dapat dihitung sehingga kembali ke rantai dengan hasil yang Anda dapatkan:

 sumInts(a: 6, b: 5) = 0
 sumInts(a: 5, b: 5) = 5 + 0 = 5
 sumInts(a: 4, b: 5) = 4 + 5 = 9
 sumInts(a: 3, b: 5) = 3 + 9 = 12
 sumInts(a: 2, b: 5) = 2 + 12 = 14.

Cara lain untuk merepresentasikan struktur rekursi:

 sumInts(a: 2, b: 5) = 2 + sumInts(a: 3, b: 5)
 sumInts(a: 2, b: 5) = 2 + 3 + sumInts(a: 4, b: 5)  
 sumInts(a: 2, b: 5) = 2 + 3 + 4 + sumInts(a: 5, b: 5)  
 sumInts(a: 2, b: 5) = 2 + 3 + 4 + 5 + sumInts(a: 6, b: 5)
 sumInts(a: 2, b: 5) = 2 + 3 + 4 + 5 + 0
 sumInts(a: 2, b: 5) = 14 
rampok
sumber
2
Sangat bagus, Rob. Anda telah menjelaskannya dengan sangat jelas dan mudah dimengerti. Terima kasih telah meluangkan waktu!
Jason Elwood
3
Ini adalah representasi paling jelas dari apa yang sedang terjadi, tanpa membahas teori dan detail teknisnya, menunjukkan setiap langkah eksekusi dengan jelas.
Bryan
2
Saya senang. :) tidak selalu mudah untuk menjelaskan hal-hal ini. Terima kasih atas pujiannya.
Rob
1
+1. Beginilah cara saya menjelaskannya, khususnya dengan contoh struktur terakhir Anda. Sangat membantu untuk membuka gulungan secara visual apa yang terjadi.
KChaloux
40

Rekursi adalah topik yang sulit untuk dipahami dan saya rasa saya tidak dapat sepenuhnya melakukannya dengan adil di sini. Sebaliknya, saya akan mencoba untuk fokus pada bagian kode tertentu yang Anda miliki di sini dan mencoba menjelaskan intuisi mengapa solusi tersebut bekerja dan mekanisme bagaimana kode tersebut menghitung hasilnya.

Kode yang Anda berikan di sini memecahkan masalah berikut: Anda ingin mengetahui jumlah semua bilangan bulat dari a hingga b, inklusif. Sebagai contoh, Anda ingin menjumlahkan angka dari 2 hingga 5, inklusif, yaitu

2 + 3 + 4 + 5

Saat mencoba memecahkan masalah secara rekursif, salah satu langkah pertama yang harus dilakukan adalah mencari cara untuk memecah masalah menjadi masalah yang lebih kecil dengan struktur yang sama. Jadi misalkan Anda ingin menjumlahkan angka dari 2 menjadi 5, inklusif. Salah satu cara untuk menyederhanakannya adalah dengan memperhatikan bahwa jumlah di atas dapat ditulis ulang sebagai

2 + (3 + 4 + 5)

Di sini, (3 + 4 + 5) kebetulan merupakan jumlah dari semua bilangan bulat antara 3 dan 5, inklusif. Dengan kata lain, jika Anda ingin mengetahui jumlah semua bilangan bulat antara 2 dan 5, mulailah dengan menghitung jumlah semua bilangan bulat antara 3 dan 5, lalu tambahkan 2.

Jadi, bagaimana Anda menghitung jumlah semua bilangan bulat antara 3 dan 5, inklusif? Nah, jumlah itu

3 + 4 + 5

yang bisa dianggap sebagai gantinya

3 + (4 + 5)

Di sini, (4 + 5) adalah jumlah dari semua bilangan bulat antara 4 dan 5, inklusif. Jadi, jika Anda ingin menghitung jumlah semua angka antara 3 dan 5, inklusif, Anda akan menghitung jumlah semua bilangan bulat antara 4 dan 5, lalu menambahkan 3.

Ada pola di sini! Jika Anda ingin menghitung jumlah bilangan bulat antara a dan b, inklusif, Anda dapat melakukan hal berikut. Pertama, hitung jumlah bilangan bulat antara a + 1 dan b, inklusif. Selanjutnya, tambahkan ke total itu. Anda akan melihat bahwa "menghitung jumlah bilangan bulat antara a + 1 dan b, inklusif" kebetulan merupakan jenis masalah yang hampir sama yang sudah kita coba selesaikan, tetapi dengan parameter yang sedikit berbeda. Daripada menghitung dari a ke b, inklusif, kami menghitung dari a + 1 ke b, inklusif. Itulah langkah rekursif - untuk memecahkan masalah yang lebih besar ("jumlah dari a ke b, inklusif"), kita mengurangi masalah ke versi yang lebih kecil dari dirinya sendiri ("jumlah dari a + 1 ke b, inklusif.").

Jika Anda melihat kode yang Anda miliki di atas, Anda akan melihat bahwa ada langkah ini di dalamnya:

return a + sumInts(a + 1, b: b)

Kode ini hanyalah terjemahan dari logika di atas - jika Anda ingin menjumlahkan dari a ke b, inklusif, mulailah dengan menjumlahkan a + 1 ke b, inklusif (itulah panggilan rekursif ke sumInts), lalu tambahkan a.

Tentu saja, dengan sendirinya pendekatan ini tidak akan berhasil. Misalnya, bagaimana Anda menghitung jumlah semua bilangan bulat antara 5 dan 5 inklusif? Nah, dengan menggunakan logika kita saat ini, Anda akan menghitung jumlah semua bilangan bulat antara 6 dan 5, inklusif, lalu menambahkan 5. Jadi bagaimana Anda menghitung jumlah semua bilangan bulat antara 6 dan 5, inklusif? Nah, menggunakan logika kita saat ini, Anda akan menghitung jumlah semua bilangan bulat antara 7 dan 5, inklusif, lalu menambahkan 6. Anda akan melihat masalah di sini - ini terus berjalan dan berlangsung!

Dalam pemecahan masalah rekursif, perlu ada beberapa cara untuk berhenti menyederhanakan masalah dan sebaliknya hanya menyelesaikannya secara langsung. Biasanya, Anda akan menemukan kasus sederhana di mana jawabannya dapat ditentukan dengan segera, kemudian susun solusi Anda untuk menyelesaikan kasus sederhana secara langsung saat muncul. Ini biasanya disebut kasus dasar atau basis rekursif .

Jadi apa kasus dasar dalam masalah khusus ini? Saat Anda menjumlahkan bilangan bulat dari a ke b, inklusif, jika a ternyata lebih besar dari b, maka jawabannya adalah 0 - tidak ada angka dalam rentang tersebut! Oleh karena itu, kami akan menyusun solusi kami sebagai berikut:

  1. Jika a> b, maka jawabannya 0.
  2. Jika tidak (a ≤ b), dapatkan jawabannya sebagai berikut:
    1. Hitung jumlah bilangan bulat antara a + 1 dan b.
    2. Tambahkan a untuk mendapatkan jawabannya.

Sekarang, bandingkan pseudocode ini dengan kode Anda yang sebenarnya:

func sumInts(a: Int, b: Int) -> Int {
    if (a > b) {
        return 0
    } else {
        return a + sumInts(a + 1, b: b)
    }
}

Perhatikan bahwa hampir ada peta satu-ke-satu antara solusi yang diuraikan dalam kodesemu dan kode aktual ini. Langkah pertama adalah kasus dasar - jika Anda meminta jumlah rentang angka kosong, Anda mendapatkan 0. Jika tidak, hitung jumlah antara a + 1 dan b, lalu tambahkan a.

Sejauh ini, saya hanya memberikan ide tingkat tinggi di balik kode. Tapi Anda punya dua pertanyaan lain yang sangat bagus. Pertama, mengapa ini tidak selalu mengembalikan 0, mengingat fungsinya mengatakan mengembalikan 0 jika a> b? Kedua, dari mana sebenarnya ke-14 itu berasal? Mari kita lihat ini secara bergantian.

Mari kita coba kasus yang sangat, sangat sederhana. Apa yang terjadi jika Anda menelepon sumInts(6, 5)? Dalam kasus ini, menelusuri kode, Anda melihat bahwa fungsinya hanya mengembalikan 0. Itu hal yang benar untuk dilakukan, untuk - tidak ada angka dalam rentang. Sekarang, coba sesuatu yang lebih keras. Apa yang terjadi saat Anda menelepon sumInts(5, 5)? Nah, inilah yang terjadi:

  1. Anda menelepon sumInts(5, 5). Kami jatuh ke elsecabang, yang mengembalikan nilai `a + sumInts (6, 5).
  2. Untuk sumInts(5, 5)menentukan apa sumInts(6, 5)itu, kita perlu menghentikan apa yang sedang kita lakukan dan menelepon sumInts(6, 5).
  3. sumInts(6, 5)dipanggil. Ini memasuki ifcabang dan kembali 0. Namun, contoh sumIntsini dipanggil oleh sumInts(5, 5), sehingga nilai yang dikembalikan dikomunikasikan kembali ke sumInts(5, 5), bukan ke pemanggil tingkat atas.
  4. sumInts(5, 5)sekarang dapat menghitung 5 + sumInts(6, 5)untuk kembali 5. Kemudian mengembalikannya ke pemanggil tingkat atas.

Perhatikan bagaimana nilai 5 dibentuk di sini. Kami memulai dengan satu panggilan aktif ke sumInts. Itu memicu panggilan rekursif lain, dan nilai yang dikembalikan oleh panggilan itu mengkomunikasikan informasi kembali sumInts(5, 5). Panggilan ke sumInts(5, 5)kemudian pada gilirannya melakukan beberapa perhitungan dan mengembalikan nilai ke pemanggil.

Jika Anda mencoba ini dengan sumInts(4, 5), inilah yang akan terjadi:

  • sumInts(4, 5)mencoba untuk kembali 4 + sumInts(5, 5). Untuk melakukan itu, ia memanggil sumInts(5, 5).
    • sumInts(5, 5)mencoba untuk kembali 5 + sumInts(6, 5). Untuk melakukan itu, ia memanggil sumInts(6, 5).
    • sumInts(6, 5)mengembalikan 0 kembali ke sumInts(5, 5).</li> <li>sumInts (5, 5) now has a value forsumInts (6, 5) , namely 0. It then returns5 + 0 = 5`.
  • sumInts(4, 5)sekarang memiliki nilai untuk sumInts(5, 5), yaitu 5. Kemudian kembali 4 + 5 = 9.

Dengan kata lain, nilai yang dikembalikan dibentuk dengan menjumlahkan nilai satu per satu, setiap kali mengambil satu nilai yang dikembalikan oleh panggilan rekursif tertentu ke sumIntsdan menambahkan nilai saat ini dari a. Ketika rekursi berhenti, panggilan terdalam mengembalikan 0. Namun, nilai itu tidak segera keluar dari rantai panggilan rekursif; sebagai gantinya, itu hanya menyerahkan nilai kembali ke panggilan rekursif satu lapisan di atasnya. Dengan cara itu, setiap panggilan rekursif hanya menambahkan satu angka lagi dan mengembalikannya lebih tinggi dalam rangkaian, yang berpuncak dengan penjumlahan keseluruhan. Sebagai latihan, cobalah menelusuri ini sumInts(2, 5), yang Anda ingin mulai dengan ini.

Semoga ini membantu!

templatetypedef
sumber
3
Terima kasih telah meluangkan waktu dari hari sibuk Anda untuk membagikan jawaban yang komprehensif! Ada banyak informasi hebat di sini yang membantu saya memahami fungsi rekursif dan pasti akan membantu orang lain yang menemukan posting ini di masa depan. Terima kasih sekali lagi dan semoga harimu menyenangkan!
Jason Elwood
22

Sejauh ini Anda mendapatkan beberapa jawaban bagus, tetapi saya akan menambahkan satu jawaban lagi yang menggunakan cara berbeda.

Pertama, saya telah menulis banyak artikel tentang algoritma rekursif sederhana yang mungkin menarik bagi Anda; Lihat

http://ericlippert.com/tag/recursion/

http://blogs.msdn.com/b/ericlippert/archive/tags/recursion/

Itu dalam urutan terbaru di atas, jadi mulailah dari bawah.

Kedua, sejauh ini semua jawaban telah mendeskripsikan semantik rekursif dengan mempertimbangkan aktivasi fungsi . Setiap panggilan membuat aktivasi baru , dan panggilan rekursif dijalankan dalam konteks aktivasi ini. Itu adalah cara yang baik untuk memikirkannya, tetapi ada cara lain yang setara: teks pintar mencari dan mengganti .

Izinkan saya menulis ulang fungsi Anda menjadi bentuk yang sedikit lebih ringkas; jangan menganggap ini sebagai bahasa tertentu.

s = (a, b) => a > b ? 0 : a + s(a + 1, b)

Saya harap itu masuk akal. Jika Anda tidak terbiasa dengan operator bersyarat, ini adalah bentuknya condition ? consequence : alternativedan artinya akan menjadi jelas.

Sekarang kami ingin mengevaluasi. s(2,5) Kami melakukannya dengan melakukan penggantian panggilan secara tekstual dengan badan fungsi, lalu ganti adengan 2dan bdengan 5:

s(2, 5) 
---> 2 > 5 ? 0 : 2 + s(2 + 1, 5)

Sekarang evaluasi kondisional. Kami mengganti secara tekstual 2 > 5dengan false.

---> false ? 0 : 2 + s(2 + 1, 5)

Sekarang secara tekstual mengganti semua kondisi palsu dengan alternatif dan semua kondisi benar dengan konsekuensi. Kami hanya memiliki kondisi palsu, jadi kami mengganti ekspresi itu secara tekstual dengan alternatif:

---> 2 + s(2 + 1, 5)

Sekarang, untuk menyelamatkan saya karena harus mengetik semua +tanda itu, secara tekstual mengganti aritmatika konstan dengan nilainya. (Ini sedikit curang, tapi saya tidak ingin mengingat semua tanda kurung!)

---> 2 + s(3, 5)

Sekarang cari-dan-ganti, kali ini dengan isi untuk panggilan, 3untuk adan 5untuk b. Kami akan mengganti panggilan dalam tanda kurung:

---> 2 + (3 > 5 ? 0 : 3 + s(3 + 1, 5))

Dan sekarang kami terus melakukan langkah-langkah substitusi tekstual yang sama:

---> 2 + (false ? 0 : 3 + s(3 + 1, 5))  
---> 2 + (3 + s(3 + 1, 5))                
---> 2 + (3 + s(4, 5))                     
---> 2 + (3 + (4 > 5 ? 0 : 4 + s(4 + 1, 5)))
---> 2 + (3 + (false ? 0 : 4 + s(4 + 1, 5)))
---> 2 + (3 + (4 + s(4 + 1, 5)))
---> 2 + (3 + (4 + s(5, 5)))
---> 2 + (3 + (4 + (5 > 5 ? 0 : 5 + s(5 + 1, 5))))
---> 2 + (3 + (4 + (false ? 0 : 5 + s(5 + 1, 5))))
---> 2 + (3 + (4 + (5 + s(5 + 1, 5))))
---> 2 + (3 + (4 + (5 + s(6, 5))))
---> 2 + (3 + (4 + (5 + (6 > 5 ? 0 : s(6 + 1, 5)))))
---> 2 + (3 + (4 + (5 + (true ? 0 : s(6 + 1, 5)))))
---> 2 + (3 + (4 + (5 + 0)))
---> 2 + (3 + (4 + 5))
---> 2 + (3 + 9)
---> 2 + 12
---> 14

Yang kami lakukan di sini hanyalah substitusi tekstual langsung . Sungguh saya seharusnya tidak mengganti "3" untuk "2 + 1" dan seterusnya sampai saya harus melakukannya, tetapi secara pedagogis itu akan menjadi sulit untuk dibaca.

Aktivasi fungsi tidak lebih dari mengganti pemanggilan fungsi dengan tubuh pemanggilan, dan mengganti parameter formal dengan argumen yang sesuai. Anda harus berhati-hati memasukkan tanda kurung dengan cerdas, tetapi selain itu, itu hanya penggantian teks.

Tentu saja, sebagian besar bahasa tidak benar-benar mengimplementasikan aktivasi sebagai pengganti teks, tetapi secara logis memang begitu.

Jadi apa yang dimaksud dengan rekursi tak terbatas? Rekursi di mana substitusi tekstual tidak berhenti! Perhatikan bagaimana akhirnya kita sampai pada langkah di mana tidak ada lagi yang sharus diganti, dan kita kemudian bisa menerapkan aturan untuk aritmatika.

Eric Lippert
sumber
Contoh yang bagus tetapi menghancurkan hati Anda ketika Anda melanjutkan untuk melakukan perhitungan yang lebih kompleks. Misalnya. Menemukan leluhur yang sama di Pohon Biner.
CodeYogi
11

Cara saya biasanya mengetahui cara kerja fungsi rekursif adalah dengan melihat kasus dasar dan bekerja mundur. Inilah teknik yang diterapkan pada fungsi ini.

Pertama kasus dasar:

sumInts(6, 5) = 0

Kemudian panggilan tepat di atasnya di tumpukan panggilan :

sumInts(5, 5) == 5 + sumInts(6, 5)
sumInts(5, 5) == 5 + 0
sumInts(5, 5) == 5

Kemudian panggilan tepat di atasnya di tumpukan panggilan:

sumInts(4, 5) == 4 + sumInts(5, 5)
sumInts(4, 5) == 4 + 5
sumInts(4, 5) == 9

Dan seterusnya:

sumInts(3, 5) == 3 + sumInts(4, 5)
sumInts(3, 5) == 3 + 9
sumInts(3, 5) == 12

Dan seterusnya:

sumInts(2, 5) == 2 + sumInts(3, 5)
sumInts(4, 5) == 2 + 12
sumInts(4, 5) == 14

Perhatikan bahwa kita telah sampai pada panggilan awal kita ke fungsi tersebut sumInts(2, 5) == 14

Urutan eksekusi panggilan ini:

sumInts(2, 5)
sumInts(3, 5)
sumInts(4, 5)
sumInts(5, 5)
sumInts(6, 5)

Urutan panggilan ini kembali:

sumInts(6, 5)
sumInts(5, 5)
sumInts(4, 5)
sumInts(3, 5)
sumInts(2, 5)

Perhatikan bahwa kami sampai pada kesimpulan tentang bagaimana fungsi beroperasi dengan menelusuri panggilan dalam urutan yang mereka kembalikan .

OregonTrail
sumber
5

Saya akan mencobanya.

Dengan mengeksekusi persamaan a + sumInts (a + 1, b), saya akan menunjukkan bagaimana jawaban akhirnya adalah 14.

//the sumInts function definition
func sumInts(a: Int, b: Int) -> Int {
    if (a > b) {
        return 0
    } else {
        return a + sumInts(a + 1, b)
    }
}

Given: a = 2 and b = 5

1) 2 + sumInts(2+1, 5)

2) sumInts(3, 5) = 12
   i) 3 + sumInts(3+1, 5)
   ii) 4 + sumInts(4+1, 5)
   iii) 5 + sumInts(5+1, 5)
   iv) return 0
   v) return 5 + 0
   vi) return 4 + 5
   vii) return 3 + 9

3) 2 + 12 = 14.

Beri tahu kami jika Anda memiliki pertanyaan lebih lanjut.

Berikut contoh lain dari fungsi rekursif pada contoh berikut.

Seorang pria baru saja lulus kuliah.

t adalah jumlah waktu dalam beberapa tahun.

Jumlah tahun kerja sebenarnya sebelum pensiun, dapat dihitung sebagai berikut:

public class DoIReallyWantToKnow 
{
    public int howLongDoIHaveToWork(int currentAge)
    {
      const int DESIRED_RETIREMENT_AGE = 65;
      double collectedMoney = 0.00; //remember, you just graduated college
      double neededMoneyToRetire = 1000000.00

      t = 0;
      return work(t+1);
    }

    public int work(int time)
    {
      collectedMoney = getCollectedMoney();

      if(currentAge >= DESIRED_RETIREMENT_AGE 
          && collectedMoney == neededMoneyToRetire
      {
        return time;
      }

      return work(time + 1);
    }
}

Dan itu seharusnya cukup untuk membuat siapapun tertekan, lol. ;-P

Bryan
sumber
5

Pengulangan. Dalam rekursi Ilmu Komputer dibahas secara mendalam di bawah topik Finite Automata.

Dalam bentuknya yang paling sederhana, ini adalah referensi diri. Misalnya, mengatakan bahwa "mobil saya adalah mobil" adalah pernyataan rekursif. Masalahnya adalah bahwa pernyataan tersebut adalah rekursi tak terbatas yang tidak akan pernah berakhir. Definisi dalam pernyataan "mobil" adalah bahwa itu adalah "mobil" jadi bisa diganti. Namun, tidak ada habisnya karena dalam kasus pergantian pemain tetap menjadi "mobil saya adalah mobil".

Ini bisa berbeda jika pernyataannya adalah "mobil saya adalah bentley. Mobil saya berwarna biru." Dalam hal ini substitusi dalam situasi kedua untuk mobil dapat berupa "bentley" yang menghasilkan "bentley saya berwarna biru". Jenis substitusi ini secara matematis dijelaskan dalam Ilmu Komputer melalui Tata Bahasa Bebas Konteks .

Substitusi sebenarnya adalah aturan produksi. Mengingat bahwa pernyataan tersebut diwakili oleh S dan mobil itu adalah variabel yang dapat menjadi "bentley", pernyataan ini dapat direkonstruksi secara rekursif.

S -> "my"S | " "S | CS | "is"S | "blue"S | ε
C -> "bentley"

Ini dapat dibangun dengan berbagai cara, karena setiap |cara ada pilihan. Sdapat diganti dengan salah satu dari pilihan tersebut, dan S selalu dimulai dengan kosong. Cara εuntuk menghentikan produksi. Sama seperti yang Sbisa diganti, begitu pula variabel lain (hanya ada satu dan itu Cyang akan mewakili "bentley").

Jadi memulai dengan Smenjadi kosong, dan menggantinya dengan pilihan pertama "my"S Smenjadi

"my"S

Smasih bisa diganti karena mewakili variabel. Kita bisa memilih "saya" lagi, atau ε untuk mengakhirinya, tapi mari kita lanjutkan membuat pernyataan asli kita. Kami memilih ruang yang artinya Sdiganti" "S

"my "S

Selanjutnya mari kita pilih C.

"my "CS

Dan C hanya punya satu pilihan pengganti

"my bentley"S

Dan ruang lagi untuk S

"my bentley "S

Dan sebagainya "my bentley is"S, "my bentley is "S, "my bentley is blue"S, "my bentley is blue"(menggantikan S untuk ε berakhir produksi) dan kami telah rekursif membangun pernyataan kami "bentley saya adalah biru".

Pikirkan rekursi sebagai produksi dan penggantian ini. Setiap langkah dalam proses menggantikan pendahulunya untuk menghasilkan hasil akhir. Dalam contoh yang tepat dari jumlah rekursif dari 2 sampai 5, Anda akan mendapatkan produksi

S -> 2 + A
A -> 3 + B
B -> 4 + C
C -> 5 + D
D -> 0

Ini menjadi

2 + A
2 + 3 + B
2 + 3 + 4 + C
2 + 3 + 4 + 5 + D
2 + 3 + 4 + 5 + 0
14
Travis J
sumber
Saya tidak yakin bahwa automata keadaan terbatas atau tata bahasa bebas konteks adalah contoh terbaik yang dapat membantu seseorang membangun intuisi pertama tentang rekursi. Itu adalah contoh yang bagus, tapi mungkin agak asing bagi programmer yang tidak memiliki latar belakang CS sebelumnya.
chi
4

Saya pikir cara terbaik untuk memahami fungsi rekursif adalah menyadari bahwa mereka dibuat untuk memproses struktur data rekursif. Tetapi dalam fungsi asli Anda sumInts(a: Int, b: Int)yang menghitung secara rekursif jumlah angka dari asampai b, ini tampaknya bukan struktur data rekursif ... Mari kita coba versi yang sedikit dimodifikasi di sumInts(a: Int, n: Int)mana nadalah berapa banyak angka yang akan Anda tambahkan.

Sekarang, penjumlahan adalah rekursif n, bilangan asli. Masih bukan data rekursif, bukan? Nah, bilangan asli dapat dianggap sebagai struktur data rekursif menggunakan aksioma Peano:

enum Natural = {
    case Zero
    case Successor(Natural)
}

Jadi, 0 = Nol, 1 = Succesor (Nol), 2 = Succesor (Succesor (Nol)), dan seterusnya.

Setelah Anda memiliki struktur data rekursif, Anda memiliki templat untuk fungsi tersebut. Untuk setiap kasus non rekursif, Anda dapat menghitung nilainya secara langsung. Untuk kasus rekursif, Anda mengasumsikan bahwa fungsi rekursif sudah berfungsi dan menggunakannya untuk menghitung kasus, tetapi mendekonstruksi argumennya. Dalam kasus Natural, itu berarti bahwa alih-alih Succesor(n)kami akan menggunakan n, atau yang setara, alih-alih nkami akan menggunakan n - 1.

// sums n numbers beginning from a
func sumInts(a: Int, n: Int) -> Int {
    if (n == 0) {
        // non recursive case
    } else {
        // recursive case. We use sumInts(..., n - 1)
    }
}

Sekarang fungsi rekursif lebih sederhana untuk diprogram. Pertama, kasus dasar n=0,. Apa yang harus kita kembalikan jika kita tidak ingin menambahkan angka? Jawabannya tentu saja 0.

Bagaimana dengan kasus rekursif? Jika kita ingin menambahkan nangka yang dimulai dengan adan kita sudah memiliki sumIntsfungsi yang berfungsi n-1? Nah, kita perlu menambahkan adan kemudian memanggil sumIntsdengan a + 1, jadi kami berakhir dengan:

// sums n numbers beginning from a
func sumInts(a: Int, n: Int) -> Int {
    if (n == 0) {
        return 0
    } else {
        return a + sumInts(a + 1, n - 1)
    }
}

Hal yang menyenangkan adalah sekarang Anda tidak perlu berpikir dalam rekursi tingkat rendah. Anda hanya perlu memverifikasi bahwa:

  • Untuk kasus dasar dari data rekursif, ia menghitung jawaban tanpa menggunakan rekursi.
  • Untuk kasus rekursif dari data rekursif, ini menghitung jawaban menggunakan rekursi atas data yang dirusak.
jdinunzio.dll
sumber
4

Anda mungkin tertarik dengan implementasi fungsi Nisan dan Schocken . PDF yang ditautkan adalah bagian dari kursus online gratis. Ini menjelaskan bagian kedua dari implementasi mesin virtual di mana siswa harus menulis kompilator virtual-mesin-bahasa-ke-mesin-bahasa. Implementasi fungsi yang mereka usulkan mampu rekursi karena berbasis stack.

Untuk memperkenalkan Anda pada implementasi fungsi: Pertimbangkan kode mesin virtual berikut:

masukkan deskripsi gambar di sini

Jika Swift dikompilasi ke bahasa mesin virtual ini, maka blok kode Swift berikut:

mult(a: 2, b: 3) - 4

akan dikompilasi menjadi

push constant 2  // Line 1
push constant 3  // Line 2
call mult        // Line 3
push constant 4  // Line 4
sub              // Line 5

Bahasa mesin virtual dirancang di sekitar tumpukan global . push constant nmendorong integer ke tumpukan global ini.

Setelah menjalankan baris 1 dan 2, tumpukan terlihat seperti:

256:  2  // Argument 0
257:  3  // Argument 1

256dan 257merupakan alamat memori.

call mult mendorong nomor baris kembali (3) ke tumpukan dan mengalokasikan ruang untuk variabel lokal fungsi.

256:  2  // argument 0
257:  3  // argument 1
258:  3  // return line number
259:  0  // local 0

... dan itu masuk ke label function mult. Kode di dalamnya multdijalankan. Sebagai hasil dari mengeksekusi kode itu, kami menghitung produk dari 2 dan 3, yang disimpan dalam variabel lokal ke-0 fungsi.

256:  2  // argument 0
257:  3  // argument 1
258:  3  // return line number
259:  6  // local 0

Tepat sebelum returndari mult, Anda akan melihat baris:

push local 0  // push result

Kami akan mendorong produk ke tumpukan.

256:  2  // argument 0
257:  3  // argument 1
258:  3  // return line number
259:  6  // local 0
260:  6  // product

Ketika kami kembali, hal berikut terjadi:

  • Masukkan nilai terakhir pada tumpukan ke alamat memori dari argumen ke-0 (256 dalam kasus ini). Ini merupakan tempat yang paling nyaman untuk meletakkannya.
  • Buang semua yang ada di tumpukan ke alamat argumen ke-0.
  • Pergi-ke nomor baris kembali (3 dalam kasus ini) dan kemudian maju.

Setelah kembali kami siap untuk mengeksekusi baris 4, dan tumpukan kami terlihat seperti ini:

256:  6  // product that we just returned

Sekarang kami mendorong 4 ke tumpukan.

256:  6
257:  4

subadalah fungsi primitif dari bahasa mesin virtual. Dibutuhkan dua argumen dan mengembalikan hasilnya ke alamat biasa: argumen ke-0.

Sekarang kita punya

256:  2  // 6 - 4 = 2

Sekarang setelah Anda mengetahui cara kerja pemanggilan fungsi, memahami cara kerja rekursi relatif sederhana. Tidak ada sihir , hanya setumpuk.

Saya telah mengimplementasikan sumIntsfungsi Anda dalam bahasa mesin virtual ini:

function sumInts 0     // `0` means it has no local variables.
  label IF
    push argument 0
    push argument 1
    lte              
    if-goto ELSE_CASE
    push constant 0
    return
  label ELSE_CASE
    push constant 2
    push argument 0
    push constant 1
    add
    push argument 1
    call sumInts       // Line 15
    add                // Line 16
    return             // Line 17
// End of function

Sekarang saya akan menyebutnya:

push constant 2
push constant 5
call sumInts           // Line 21

Kode dijalankan dan kami sampai ke titik berhenti di mana ltepengembalian false. Seperti inilah tampilan tumpukan pada saat ini:

// First invocation
256:  2   // argument 0
257:  5   // argument 1
258:  21  // return line number
259:  2   // augend
// Second
260:  3   // argument 0
261:  5   // argument 1
262:  15  // return line number
263:  3   // augend
// Third
264:  4   // argument 0
265:  5   // argument 1
266:  15  // return line number
267:  4   // augend
// Fourth
268:  5   // argument 0
269:  5   // argument 1
270:  15  // return line number
271:  5   // augend
// Fifth
272:  6   // argument 0
273:  5   // argument 1
274:  15  // return line number
275:  0   // return value

Sekarang mari kita "melepas" rekursi kita. return0 dan kebagian baris 15 dan maju.

271:  5
272:  0

Baris 16: add

271:  5

Baris 17: return5 dan kebagian baris 15 dan maju.

267:  4
268:  5

Baris 16: add

267:  9

Baris 17: return9 dan kebagian baris 15 dan maju.

263:  3
264:  9

Baris 16: add

263:  12

Baris return17:12 dan pergi ke baris 15 dan maju.

259:  2
260:  12

Baris 16: add

259:  14

Baris return17:14 dan pergi ke baris 21 dan maju.

256:  14

Itu dia. Rekursi: Dimuliakan goto.

Jackson
sumber
4

Satu tip yang sangat bagus yang saya temui dalam belajar dan benar-benar memahami rekursi adalah meluangkan waktu untuk mempelajari bahasa yang tidak memiliki bentuk konstruksi lingkaran apa pun selain melalui rekursi. Dengan begitu Anda akan mendapatkan perasaan yang luar biasa tentang bagaimana MENGGUNAKAN rekursi melalui latihan.

Saya mengikuti http://www.htdp.org/ yang, selain menjadi tutorial Skema, juga merupakan pengantar yang bagus tentang cara merancang program dalam kaitannya dengan arsitektur dan desain.

Tetapi pada dasarnya, Anda perlu menginvestasikan waktu. Tanpa pemahaman yang 'tegas' tentang rekursi, algoritme tertentu, seperti penelusuran mundur, akan selalu tampak 'sulit' atau bahkan 'ajaib' bagi Anda. Jadi, bertahanlah. :-D

Saya harap ini membantu dan Semoga Sukses!

Th3Minstr3l
sumber
3

Sudah ada banyak jawaban bagus. Masih saya coba.
Saat dipanggil, fungsi mendapatkan alokasi ruang memori , yang ditumpuk di atas ruang memori fungsi pemanggil. Dalam ruang memori ini, fungsi menyimpan parameter yang diteruskan, variabel, dan nilainya. Ruang memori ini menghilang bersama dengan panggilan balik akhir dari fungsi tersebut. Saat ide stack berjalan, ruang memori dari fungsi pemanggil sekarang menjadi aktif.

Untuk panggilan rekursif, fungsi yang sama mendapatkan banyak ruang memori yang ditumpuk satu sama lain. Itu saja. Ide sederhana tentang bagaimana stack bekerja dalam memori komputer akan membantu Anda memahami bagaimana rekursi terjadi dalam implementasi.

Gulshan
sumber
3

Sedikit keluar dari topik, saya tahu, tapi ... coba cari rekursi di Google ... Anda akan melihat dengan contoh apa artinya :-)


Versi Google sebelumnya menampilkan teks berikut (dikutip dari memori):

Pengulangan

Lihat Rekursi

Pada 10 September 2014, lelucon tentang rekursi telah diperbarui:

Pengulangan

Apakah maksud Anda: Rekursi


Untuk balasan lainnya, lihat jawaban ini .

Pierre Arnaud
sumber
3

Pikirkan rekursi sebagai beberapa klon melakukan hal yang sama ...

Anda meminta untuk menggandakan [1]: "jumlahkan angka antara 2 dan 5"

+ clone[1]               knows that: result is 2 + "sum numbers between 3 and 5". so he asks to clone[2] to return: "sum numbers between 3 and 5"
|   + clone[2]           knows that: result is 3 + "sum numbers between 4 and 5". so he asks to clone[3] to return: "sum numbers between 4 and 5"
|   |   + clone[3]       knows that: result is 4 + "sum numbers between 5 and 5". so he asks to clone[4] to return: "sum numbers between 5 and 5"
|   |   |   + clone[4]   knows that: result is 5 + "sum numbers between 6 and 5". so he asks to clone[5] to return: "sum numbers between 6 and 5"
|   |   |   |   clone[5] knows that: he can't sum, because 6 is larger than 5. so he returns 0 as result.
|   |   |   + clone[4]   gets the result from clone[5] (=0)  and sums: 5 + 0,  returning 5
|   |   + clone[3]       gets the result from clone[4] (=5)  and sums: 4 + 5,  returning 9
|   + clone[2]           gets the result from clone[3] (=9)  and sums: 3 + 9,  returning 12
+ clone[1]               gets the result from clone[2] (=12) and sums: 2 + 12, returning 14

dan voilá !!

Kristen
sumber
2

Banyak dari jawaban di atas sangat bagus. Sebuah teknik yang berguna untuk memecahkan rekursi adalah dengan menguraikan terlebih dahulu apa yang ingin kita lakukan dan kode sebagai manusia akan menyelesaikannya. Dalam kasus di atas, kami ingin meringkas urutan bilangan bulat berurutan (menggunakan angka dari atas):

2, 3, 4, 5  //adding these numbers would sum to 14

Sekarang, perhatikan bahwa baris-baris ini membingungkan (tidak salah, tapi membingungkan).

if (a > b) {
    return 0 
}

Kenapa di tes a>b?, Dan kenapareturn 0

Mari kita ubah kodenya untuk mencerminkan lebih dekat apa yang dilakukan manusia

func sumInts(a: Int, b: Int) -> Int {
  if (a == b) {
    return b // When 'a equals b' I'm at the most Right integer, return it
  }
  else {
    return a + sumInts(a: a + 1, b: b)
  }
}

Bisakah kita melakukannya lebih seperti manusia? Iya! Biasanya kami menjumlahkan dari kiri ke kanan (2 + 3 + ...). Tetapi rekursi di atas menjumlahkan dari kanan ke kiri (... + 4 + 5). Ubah kode untuk mencerminkannya ( -Bisa sedikit mengintimidasi, tapi tidak banyak)

func sumInts(a: Int, b: Int) -> Int {
  if (a == b) {
    return b // When I'm at the most Left integer, return it
  }
  else {
    return sumInts(a: a, b: b - 1) + b
  }
}

Beberapa orang mungkin menganggap fungsi ini lebih membingungkan karena kita mulai dari ujung 'jauh', tetapi berlatih bisa membuatnya terasa alami (dan ini adalah teknik 'berpikir' bagus lainnya: Mencoba 'kedua' sisi saat menyelesaikan rekursi). Dan lagi, fungsi tersebut mencerminkan apa yang dilakukan oleh manusia (paling?): Mengambil jumlah semua bilangan bulat kiri dan menambahkan bilangan bulat kanan 'berikutnya'.

pengguna6085241
sumber
2

Saya mengalami kesulitan untuk memahami rekursi kemudian saya menemukan blog ini dan saya sudah melihat pertanyaan ini jadi saya pikir saya harus berbagi. Anda harus membaca blog ini. Saya menemukan ini sangat membantu menjelaskannya dengan tumpukan dan bahkan menjelaskan bagaimana dua rekursi bekerja dengan tumpukan langkah demi langkah. Saya sarankan Anda terlebih dahulu memahami cara kerja tumpukan yang dijelaskannya dengan sangat baik di sini: perjalanan-ke-tumpukan

then now you will understand how recursion works now take a look of this post: Pahami rekursi langkah demi langkah

masukkan deskripsi gambar di sini

Ini adalah program:

def hello(x):
    if x==1:
        return "op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(3)

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini


sumber
2

Rekursi mulai masuk akal bagi saya ketika saya berhenti membaca apa yang orang lain katakan tentangnya atau melihatnya sebagai sesuatu yang dapat saya hindari dan hanya menulis kode. Saya menemukan masalah dengan solusi dan mencoba menduplikasi solusi tanpa melihat. Saya hanya melihat solusinya ketika saya terjebak tanpa daya. Kemudian saya kembali mencoba menduplikasinya. Saya melakukan ini lagi pada banyak masalah sampai saya mengembangkan pemahaman dan pemahaman saya sendiri tentang bagaimana mengidentifikasi masalah yang berulang dan menyelesaikannya. Ketika saya mencapai level ini, saya mulai mengarang masalah dan menyelesaikannya. Itu lebih membantu saya. Kadang-kadang, hal-hal hanya bisa dipelajari dengan mencobanya sendiri dan berjuang; sampai Anda "mendapatkannya".

Phil
sumber
0

Izinkan saya memberi tahu Anda dengan contoh deret Fibonacci, Fibonacci adalah

t (n) = t (n - 1) + n;

jika n = 0 maka 1

jadi mari kita lihat cara kerja rekursi, saya hanya mengganti ndi t(n)dengan n-1dan sebagainya. terlihat:

t (n-1) = t (n - 2) + n + 1;

t (n-1) = t (n - 3) + n + 1 + n;

t (n-1) = t (n - 4) + n + 1 + n + 2 + n;

.

.

.

t (n) = t (nk) + ... + (nk-3) + (nk-2) + (nk-1) + n;

kita tahu jika t(0)=(n-k)sederajat untuk 1kemudian n-k=0jadi n=kkita ganti kdengan n:

t (n) = t (nn) + ... + (n-n + 3) + (n-n + 2) + (n-n + 1) + n;

jika kita menghilangkan n-nmaka:

t (n) = t (0) + ... + 3 + 2 + 1 + (n-1) + n;

begitu 3+2+1+(n-1)+njuga bilangan asli. itu dihitung sebagaiΣ3+2+1+(n-1)+n = n(n+1)/2 => n²+n/2

hasil untuk fib adalah: O(1 + n²) = O(n²)

Ini cara terbaik untuk memahami hubungan rekursif

Alireza Rahmani Khalili
sumber