Bagaimana Anda mengosongkan vas berisi lima bunga?
Jawab: jika vas tidak kosong, Anda mengambil satu bunga dan kemudian Anda mengosongkan vas berisi empat bunga.
Bagaimana Anda mengosongkan vas berisi empat bunga?
Jawab: jika vas tidak kosong, Anda mengambil satu bunga dan kemudian Anda mengosongkan vas berisi tiga bunga.
Bagaimana Anda mengosongkan vas berisi tiga bunga?
Jawab: jika vas tidak kosong, Anda mengambil satu bunga dan kemudian Anda mengosongkan vas berisi dua bunga.
Bagaimana Anda mengosongkan vas berisi dua bunga?
Jawab: jika vas tidak kosong, Anda mengambil satu bunga dan kemudian Anda mengosongkan vas yang berisi satu bunga.
Bagaimana Anda mengosongkan vas berisi satu bunga?
Jawab: jika vas tidak kosong, Anda mengambil satu bunga dan kemudian Anda mengosongkan vas yang tidak mengandung bunga.
Bagaimana Anda mengosongkan vas tanpa bunga?
Jawab: jika vas tidak kosong, Anda mengambil satu bunga tetapi vas itu kosong sehingga Anda selesai.
Itu berulang. Mari kita generalisasikan:
Bagaimana Anda mengosongkan vas berisi N bunga?
Jawab: jika vas tidak kosong, Anda mengambil satu bunga dan kemudian Anda mengosongkan vas berisi N-1 bunga.
Hmm, bisakah kita melihatnya dalam kode?
void emptyVase( int flowersInVase ) {
if( flowersInVase > 0 ) {
// take one flower and
emptyVase( flowersInVase - 1 ) ;
} else {
// the vase is empty, nothing to do
}
}
Hmm, tidak bisakah kita melakukan itu dalam for for loop?
Mengapa, ya, rekursi dapat diganti dengan iterasi, tetapi seringkali rekursi lebih elegan.
Mari kita bicara tentang pohon. Dalam ilmu komputer, pohon adalah struktur yang terdiri dari node , di mana setiap node memiliki beberapa anak yang juga node, atau nol. Sebuah pohon biner adalah pohon yang terbuat dari node yang memiliki tepat dua anak, biasanya disebut "kiri" dan "kanan"; lagi-lagi anak-anak bisa menjadi simpul, atau nol. Sebuah akar adalah simpul yang bukan anak dari node lain.
Bayangkan sebuah simpul, di samping anak-anaknya, memiliki nilai, angka, dan bayangkan bahwa kami ingin menjumlahkan semua nilai di beberapa pohon.
Untuk menjumlahkan nilai dalam satu simpul, kami akan menambahkan nilai simpul itu sendiri ke nilai anak kirinya, jika ada, dan nilai anak kanannya, jika ada. Sekarang ingat bahwa anak-anak, jika mereka bukan nol, juga node.
Jadi untuk menjumlahkan anak kiri, kita akan menambahkan nilai simpul anak itu sendiri ke nilai anak kirinya, jika ada, dan nilai anak kanannya, jika ada.
Jadi untuk menjumlahkan nilai anak kiri anak kiri, kita akan menambahkan nilai simpul anak itu sendiri ke nilai anak kirinya, jika ada, dan nilai anak kanannya, jika ada.
Mungkin Anda sudah mengantisipasi ke mana saya akan pergi dengan ini, dan ingin melihat beberapa kode? BAIK:
struct node {
node* left;
node* right;
int value;
} ;
int sumNode( node* root ) {
// if there is no tree, its sum is zero
if( root == null ) {
return 0 ;
} else { // there is a tree
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
}
}
Perhatikan bahwa alih-alih secara eksplisit menguji anak-anak untuk melihat apakah mereka nol atau node, kami hanya membuat fungsi rekursif mengembalikan nol untuk simpul nol.
Jadi katakan kita memiliki pohon yang terlihat seperti ini (angka-angka adalah nilai, garis miring menunjuk ke anak-anak, dan @ berarti pointer menunjuk ke nol):
5
/ \
4 3
/\ /\
2 1 @ @
/\ /\
@@ @@
Jika kita memanggil sumNode pada root (simpul dengan nilai 5), kita akan kembali:
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
return 5 + sumNode( node-with-value-4 ) + sumNode( node-with-value-3 ) ;
Mari kita kembangkan itu di tempatnya. Di mana-mana kita melihat sumNode, kita akan menggantinya dengan perluasan pernyataan kembali:
sumNode( node-with-value-5);
return root->value + sumNode( root->left ) + sumNode( root->right ) ;
return 5 + sumNode( node-with-value-4 ) + sumNode( node-with-value-3 ) ;
return 5 + 4 + sumNode( node-with-value-2 ) + sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + sumNode(null ) + sumNode( null )
+ sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ sumNode( node-with-value-1 )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + sumNode(null ) + sumNode( null )
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ sumNode( node-with-value-3 ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 + sumNode(null ) + sumNode( null ) ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 + 0 + 0 ;
return 5 + 4
+ 2 + 0 + 0
+ 1 + 0 + 0
+ 3 ;
return 5 + 4
+ 2 + 0 + 0
+ 1
+ 3 ;
return 5 + 4
+ 2
+ 1
+ 3 ;
return 5 + 4
+ 3
+ 3 ;
return 5 + 7
+ 3 ;
return 5 + 10 ;
return 15 ;
Sekarang lihat bagaimana kita menaklukkan struktur kedalaman dan "percabangan" yang sewenang-wenang, dengan menganggapnya sebagai penerapan berulang template komposit? setiap kali melalui fungsi sumNode kami, kami hanya berurusan dengan satu node, menggunakan cabang if / then tunggal, dan dua pernyataan pengembalian sederhana yang hampir membuat repot, langsung dari spesifikasi kami?
How to sum a node:
If a node is null
its sum is zero
otherwise
its sum is its value
plus the sum of its left child node
plus the sum of its right child node
Itulah kekuatan rekursi.
Contoh vas di atas adalah contoh rekursi ekor . Semua yang dimaksud dengan rekursi ekor adalah bahwa dalam fungsi rekursif, jika kita berulang (yaitu, jika kita memanggil fungsi lagi), itu adalah hal terakhir yang kami lakukan.
Contoh pohon bukanlah rekursif ekor, karena meskipun hal terakhir yang kami lakukan adalah mengembalikan anak yang tepat, sebelum kami melakukannya, kami mengulangi anak kiri.
Bahkan, urutan di mana kami memanggil anak-anak, dan menambahkan nilai node saat ini tidak masalah sama sekali, karena penambahan bersifat komutatif.
Sekarang mari kita lihat operasi di mana pesanan itu penting. Kami akan menggunakan pohon biner node, tetapi kali ini nilai yang dimiliki akan menjadi karakter, bukan angka.
Pohon kami akan memiliki properti khusus, bahwa untuk setiap simpul, karakternya muncul setelah (dalam urutan abjad) karakter yang dipegang oleh anak kirinya dan sebelum (dalam urutan abjad) karakter yang dipegang oleh anak kanannya.
Apa yang ingin kita lakukan adalah mencetak pohon dalam urutan abjad. Itu mudah dilakukan, mengingat properti khusus pohon. Kami hanya mencetak anak kiri, lalu karakter simpul, lalu anak kanan.
Kami tidak hanya ingin mencetak mau tak mau, jadi kami akan melewati fungsi kami sesuatu untuk dicetak. Ini akan menjadi objek dengan fungsi cetak (char); kita tidak perlu khawatir tentang cara kerjanya, hanya ketika cetak dipanggil, itu akan mencetak sesuatu, di suatu tempat.
Mari kita lihat dalam kode:
struct node {
node* left;
node* right;
char value;
} ;
// don't worry about this code
class Printer {
private ostream& out;
Printer( ostream& o ) :out(o) {}
void print( char c ) { out << c; }
}
// worry about this code
int printNode( node* root, Printer& printer ) {
// if there is no tree, do nothing
if( root == null ) {
return ;
} else { // there is a tree
printNode( root->left, printer );
printer.print( value );
printNode( root->right, printer );
}
Printer printer( std::cout ) ;
node* root = makeTree() ; // this function returns a tree, somehow
printNode( root, printer );
Selain urutan operasi yang sekarang penting, contoh ini menggambarkan bahwa kita dapat meneruskan berbagai hal ke fungsi rekursif. Satu-satunya hal yang harus kita lakukan adalah memastikan bahwa pada setiap panggilan rekursif, kita terus meneruskannya. Kami melewati penunjuk simpul dan printer ke fungsi, dan pada setiap panggilan rekursif, kami melewati mereka "turun".
Sekarang jika pohon kita terlihat seperti ini:
k
/ \
h n
/\ /\
a j @ @
/\ /\
@@ i@
/\
@@
Apa yang akan kita cetak?
From k, we go left to
h, where we go left to
a, where we go left to
null, where we do nothing and so
we return to a, where we print 'a' and then go right to
null, where we do nothing and so
we return to a and are done, so
we return to h, where we print 'h' and then go right to
j, where we go left to
i, where we go left to
null, where we do nothing and so
we return to i, where we print 'i' and then go right to
null, where we do nothing and so
we return to i and are done, so
we return to j, where we print 'j' and then go right to
null, where we do nothing and so
we return to j and are done, so
we return to h and are done, so
we return to k, where we print 'k' and then go right to
n where we go left to
null, where we do nothing and so
we return to n, where we print 'n' and then go right to
null, where we do nothing and so
we return to n and are done, so
we return to k and are done, so we return to the caller
Jadi jika kita hanya melihat garis-garisnya maka kita dicetak:
we return to a, where we print 'a' and then go right to
we return to h, where we print 'h' and then go right to
we return to i, where we print 'i' and then go right to
we return to j, where we print 'j' and then go right to
we return to k, where we print 'k' and then go right to
we return to n, where we print 'n' and then go right to
Kita lihat kita mencetak "ahijkn", yang memang dalam urutan abjad.
Kami berhasil mencetak seluruh pohon, dalam urutan abjad, hanya dengan mengetahui cara mencetak satu simpul dalam urutan abjad. Yang mana saja (karena pohon kami memiliki properti khusus nilai pemesanan di sebelah kiri dari nilai yang menurut abjad kemudian) mengetahui untuk mencetak anak kiri sebelum mencetak nilai node, dan untuk mencetak anak yang tepat setelah mencetak nilai node.
Dan itulah kekuatan rekursi: mampu melakukan semua hal dengan mengetahui hanya bagaimana melakukan sebagian dari keseluruhan (dan mengetahui kapan harus berhenti berulang).
Mengingat hal itu di sebagian besar bahasa, operator || ("atau") korsleting ketika operan pertamanya benar, fungsi rekursif umum adalah:
void recurse() { doWeStop() || recurse(); }
Komentar Luc M:
SO harus membuat lencana untuk jawaban semacam ini. Selamat!
Terima kasih, Luc! Tapi, sebenarnya, karena saya mengedit jawaban ini lebih dari empat kali (untuk menambahkan contoh terakhir, tetapi kebanyakan untuk memperbaiki kesalahan ketik dan memolesnya - mengetik pada keyboard netbook kecil itu sulit), saya tidak bisa mendapatkan poin lagi untuk itu . Yang agak mengecilkan hati saya dari berusaha sebanyak mungkin ke jawaban masa depan.
Lihat komentar saya di sini tentang itu: /programming/128434/what-are-community-wiki-posts-in-stackoverflow/718699#718699