rev4: Sebuah komentar yang sangat fasih oleh pengguna Sammaron telah mencatat bahwa, mungkin, jawaban ini sebelumnya membingungkan top-down dan bottom-up. Meskipun awalnya jawaban ini (rev3) dan jawaban lain mengatakan bahwa "bottom-up adalah memoisasi" ("asumsikan subproblem"), itu mungkin kebalikannya (yaitu, "top-down" mungkin "menganggap subproblem" dan " bottom-up "mungkin" menyusun subproblem "). Sebelumnya, saya telah membaca tentang memoisasi sebagai jenis pemrograman dinamis yang berbeda dengan subtipe pemrograman dinamis. Saya mengutip sudut pandang itu meskipun tidak berlangganan. Saya telah menulis ulang jawaban ini sebagai agnostik dari terminologi sampai referensi yang tepat dapat ditemukan dalam literatur. Saya juga telah mengubah jawaban ini ke wiki komunitas. Silakan pilih sumber akademis. Daftar referensi:} {Sastra: 5 }
Rekap
Pemrograman dinamis adalah semua tentang memesan komputasi Anda dengan cara yang menghindari penghitungan ulang pekerjaan duplikat. Anda memiliki masalah utama (akar pohon submasalah Anda), dan submasalah (sub pohon). Submasalah biasanya berulang dan tumpang tindih .
Sebagai contoh, perhatikan contoh favorit Anda dari Fibonnaci. Ini adalah pohon penuh masalah, jika kami melakukan panggilan rekursif yang naif:
TOP of the tree
fib(4)
fib(3)...................... + fib(2)
fib(2)......... + fib(1) fib(1)........... + fib(0)
fib(1) + fib(0) fib(1) fib(1) fib(0)
fib(1) fib(0)
BOTTOM of the tree
(Dalam beberapa masalah langka lainnya, pohon ini bisa menjadi tak terbatas di beberapa cabang, mewakili non-pemutusan, dan dengan demikian bagian bawah pohon mungkin sangat besar. Selanjutnya, dalam beberapa masalah Anda mungkin tidak tahu seperti apa bentuk pohon penuh di depan Oleh karena itu, Anda mungkin perlu strategi / algoritma untuk memutuskan subproblem mana yang akan diungkapkan.)
Memoisasi, Tabulasi
Setidaknya ada dua teknik utama pemrograman dinamis yang tidak saling eksklusif:
Memoisasi - Ini adalah pendekatan laissez-faire: Anda menganggap bahwa Anda telah menghitung semua sub-masalah dan bahwa Anda tidak tahu apa urutan evaluasi yang optimal. Biasanya, Anda akan melakukan panggilan rekursif (atau setara berulang) dari root, dan baik berharap Anda akan mendekati urutan evaluasi optimal, atau mendapatkan bukti bahwa Anda akan membantu Anda tiba di urutan evaluasi optimal. Anda akan memastikan bahwa panggilan rekursif tidak pernah menghitung ulang subproblem karena Anda men - cache hasil, dan dengan demikian duplikat sub-pohon tidak dihitung ulang.
- contoh: Jika Anda menghitung urutan Fibonacci
fib(100)
, Anda hanya akan memanggil ini, dan itu akan memanggil fib(100)=fib(99)+fib(98)
, yang akan memanggil fib(99)=fib(98)+fib(97)
, ... dll ..., yang akan memanggil fib(2)=fib(1)+fib(0)=1+0=1
. Maka akhirnya akan menyelesaikan fib(3)=fib(2)+fib(1)
, tetapi tidak perlu menghitung ulang fib(2)
, karena kami menyimpannya.
- Ini dimulai di bagian atas pohon dan mengevaluasi subproblem dari daun / sub pohon kembali ke arah akar.
Tabulasi - Anda juga dapat menganggap pemrograman dinamis sebagai algoritma "pengisian tabel" (meskipun biasanya multidimensi, 'tabel' ini mungkin memiliki geometri non-Euclidean dalam kasus yang sangat jarang *). Ini seperti memoisasi tetapi lebih aktif, dan melibatkan satu langkah tambahan: Anda harus memilih, sebelumnya, urutan yang tepat di mana Anda akan melakukan perhitungan. Ini seharusnya tidak menyiratkan bahwa urutannya harus statis, tetapi Anda memiliki lebih banyak fleksibilitas daripada memoisasi.
- Contoh: Jika Anda melakukan fibonacci, Anda dapat memilih untuk menghitung angka-angka dalam urutan ini:
fib(2)
, fib(3)
, fib(4)
... caching setiap nilai sehingga Anda dapat menghitung yang berikutnya lebih mudah. Anda juga dapat menganggapnya sebagai mengisi tabel (bentuk caching lainnya).
- Saya pribadi tidak banyak mendengar kata 'tabulasi', tapi ini istilah yang sangat bagus. Beberapa orang menganggap ini "pemrograman dinamis".
- Sebelum menjalankan algoritma, programmer mempertimbangkan seluruh pohon, kemudian menulis algoritma untuk mengevaluasi subproblem dalam urutan tertentu menuju root, umumnya mengisi tabel.
- * catatan kaki: Kadang-kadang 'tabel' bukan tabel persegi panjang dengan konektivitas seperti jaringan, per se. Sebaliknya, mungkin memiliki struktur yang lebih rumit, seperti pohon, atau struktur khusus untuk domain masalah (misalnya kota dalam jarak terbang di peta), atau bahkan diagram teralis, yang, seperti grid, tidak memiliki struktur konektivitas atas-bawah-kiri-kanan, dll. Sebagai contoh, user3290797 menautkan contoh pemrograman dinamis untuk menemukan set independen maksimum dalam pohon , yang sesuai dengan mengisi kekosongan di pohon.
(Pada umumnya, dalam paradigma "pemrograman dinamis", saya akan mengatakan programmer menganggap seluruh pohon, lalumenulis sebuah algoritma yang mengimplementasikan strategi untuk mengevaluasi subproblem yang dapat mengoptimalkan properti apa pun yang Anda inginkan (biasanya kombinasi dari kompleksitas waktu dan kompleksitas ruang). Strategi Anda harus dimulai di suatu tempat, dengan beberapa masalah tertentu, dan mungkin dapat menyesuaikan diri berdasarkan hasil evaluasi tersebut. Dalam pengertian umum "pemrograman dinamis", Anda mungkin mencoba untuk men-cache subproblem ini, dan lebih umum, cobalah menghindari mengunjungi kembali subproblem dengan perbedaan halus mungkin menjadi kasus grafik di berbagai struktur data. Sangat sering, struktur data ini pada intinya seperti array atau tabel. Solusi untuk submasalah dapat dibuang jika kita tidak membutuhkannya lagi.)
[Sebelumnya, jawaban ini membuat pernyataan tentang terminologi top-down vs bottom-up; jelas ada dua pendekatan utama yang disebut Memoisasi dan Tabulasi yang mungkin bertentangan dengan istilah-istilah tersebut (meskipun tidak sepenuhnya). Istilah umum yang digunakan kebanyakan orang adalah "Pemrograman Dinamis" dan beberapa orang mengatakan "Memoisasi" untuk merujuk pada subtipe khusus "Pemrograman Dinamis." Jawaban ini menolak untuk mengatakan mana yang top-down dan bottom-up sampai komunitas dapat menemukan referensi yang tepat dalam makalah akademik. Pada akhirnya, penting untuk memahami perbedaan daripada terminologi.]
Pro dan kontra
Kemudahan coding
Memoisasi sangat mudah untuk dikodekan (Anda biasanya dapat * menulis anotasi "memoizer" atau fungsi pembungkus yang secara otomatis melakukannya untuk Anda), dan harus menjadi garis pendekatan pertama Anda. Kelemahan dari tabulasi adalah Anda harus membuat pemesanan.
* (ini sebenarnya hanya mudah jika Anda menulis sendiri fungsinya, dan / atau mengkode dalam bahasa pemrograman yang tidak murni / tidak berfungsi ... misalnya jika seseorang sudah menulis fib
fungsi yang dikompilasi , itu harus membuat panggilan rekursif untuk dirinya sendiri, dan Anda tidak dapat secara ajaib memoize fungsi tanpa memastikan panggilan rekursif itu memanggil fungsi memoized baru Anda (dan bukan fungsi yang tidak diememoasi asli))
Kekambuhan
Perhatikan bahwa top-down dan bottom-up dapat diimplementasikan dengan rekursi atau pengisian tabel berulang, meskipun mungkin tidak alami.
Kekhawatiran praktis
Dengan memoisasi, jika pohonnya sangat dalam (mis. fib(10^6)
), Anda akan kehabisan ruang stack, karena setiap perhitungan yang tertunda harus diletakkan di stack, dan Anda akan memiliki 10 ^ 6 dari mereka.
Optimalitas
Salah satu pendekatan mungkin tidak optimal-waktu jika pesanan Anda (atau mencoba) mengunjungi subproblem tidak optimal, khususnya jika ada lebih dari satu cara untuk menghitung subproblem (biasanya caching akan menyelesaikan ini, tetapi secara teori dimungkinkan bahwa caching mungkin tidak dalam beberapa kasus eksotis). Memoisasi biasanya akan menambah kompleksitas waktu Anda ke kompleksitas ruang Anda (misalnya dengan tabulasi Anda memiliki lebih banyak kebebasan untuk membuang perhitungan, seperti menggunakan tabulasi dengan Fib memungkinkan Anda menggunakan ruang O (1), tetapi memoisasi dengan Fib menggunakan O (N) tumpukan ruang).
Optimalisasi tingkat lanjut
Jika Anda juga melakukan masalah yang sangat rumit, Anda mungkin tidak punya pilihan selain melakukan tabulasi (atau setidaknya mengambil peran lebih aktif dalam mengarahkan memoisasi ke tempat yang Anda inginkan). Juga jika Anda berada dalam situasi di mana optimasi benar-benar kritis dan Anda harus mengoptimalkan, tabulasi akan memungkinkan Anda untuk melakukan optimasi mana memoisasi tidak akan membiarkan Anda melakukannya dengan cara yang waras. Menurut pendapat saya yang sederhana, dalam rekayasa perangkat lunak normal, tak satu pun dari kedua kasus ini pernah muncul, jadi saya hanya akan menggunakan memoisasi ("fungsi yang menyimpan jawabannya") kecuali jika sesuatu (seperti ruang stack) membuat tabulasi diperlukan ... meskipun secara teknis untuk menghindari ledakan tumpukan Anda dapat 1) meningkatkan batas ukuran tumpukan dalam bahasa yang memungkinkannya, atau 2) memakan faktor konstan dari pekerjaan ekstra untuk memvirtualisasikan tumpukan Anda (ick),
Contoh yang lebih rumit
Di sini kami mendaftar contoh-contoh yang menarik, yang bukan hanya masalah DP umum, tetapi yang menarik membedakan memoisasi dan tabulasi. Misalnya, satu formulasi mungkin jauh lebih mudah daripada yang lain, atau mungkin ada optimasi yang pada dasarnya memerlukan tabulasi:
- algoritma untuk menghitung jarak edit [ 4 ], menarik sebagai contoh non-sepele dari algoritma pengisian tabel dua dimensi