The bottom-up pendekatan (untuk pemrograman dinamis) terdiri dalam pertama melihat "kecil" sub-sub permasalahan, dan kemudian memecahkan sub-sub permasalahan yang lebih besar menggunakan solusi untuk masalah kecil.
The top-down terdiri dalam memecahkan masalah dalam "dengan cara alami" dan memeriksa apakah anda telah menghitung solusi untuk subproblem sebelumnya.
I'm sedikit bingung. Apa perbedaan antara keduanya?
rev4: sangat fasih komentar oleh pengguna Sammaron telah mencatat bahwa, mungkin, ini jawaban sebelumnya bingung top-down dan bottom-up. Sementara awalnya jawaban ini (rev3) dan jawaban yang lain mengatakan bahwa "bottom-up adalah memoization" ("menganggap sub-sub permasalahan"), mungkin terbalik (yaitu, "top-down" mungkin "menganggap sub-sub permasalahan" dan "bottom-up" mungkin "menyusun sub-sub permasalahan"). Sebelumnya, saya telah membaca di memoization menjadi berbagai jenis pemrograman dinamis sebagai lawan subtipe dari pemrograman dinamis. Saya mengutip bahwa sudut pandang meskipun tidak berlangganan itu. Saya telah ditulis ulang jawaban ini untuk menjadi agnostik terminologi sampai tepat referensi dapat ditemukan dalam literatur. Saya juga telah dikonversi jawaban ini ke komunitas wiki. Silahkan sukai sumber akademik. Daftar referensi: {Web: 1,2} {Sastra: 5}
Rekap
Pemrograman dinamis adalah semua tentang memesan perhitungan dengan cara yang menghindari menghitung ulang duplikasi pekerjaan. Anda memiliki masalah utama (akar pohon anda dari sub-sub permasalahan), dan sub-sub permasalahan (subtrees). Sub-sub permasalahan yang biasanya berulang dan tumpang tindih. Misalnya, pertimbangkan favorit anda contoh Fibonnaci. Ini adalah penuh pohon dari sub-sub permasalahan, jika kita tidak naif panggilan rekursif:
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
Setidaknya ada dua teknik utama dari pemrograman dinamis yang tidak saling eksklusif:
fib(100)
, anda hanya akan panggilan 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)
, tapi itu doesn't perlu menghitung ulang fib(2)
, karena kita cache. fib(2)
,fib(3)
,fib(4)
... caching setiap nilai sehingga anda dapat menghitung berikutnya yang lebih mudah. Anda juga dapat menganggapnya sebagai mengisi tabel (bentuk lain dari caching). Memoization sangat mudah untuk kode (anda dapat umumnya menulis "memoizer" penjelasan atau pembungkus fungsi yang secara otomatis melakukannya untuk anda), dan harus menjadi baris pertama dari pendekatan. Kelemahan dari tabulasi adalah bahwa anda harus datang dengan pemesanan.
(ini sebenarnya mudah hanya jika anda menulis fungsi sendiri, dan/atau coding di murni/non-fungsional bahasa pemrograman... misalnya jika seseorang sudah menulis precompiled bikinan
fungsi, hal itu tentu membuat panggilan rekursif untuk dirinya sendiri, dan anda dapat't ajaib memoize fungsi tanpa membuat orang-orang rekursif panggilan panggilan anda baru memoized fungsi (dan tidak asli unmemoized fungsi))
Perhatikan bahwa kedua top-down dan bottom-up dapat dilaksanakan dengan rekursif atau iteratif meja-mengisi, meskipun mungkin tidak alami.
Dengan memoization, jika pohon yang sangat mendalam (misalnya fib(10^6)
), anda akan berjalan keluar dari ruang stack, karena masing-masing tertunda perhitungan harus diletakkan pada stack, dan anda akan memiliki 10^6 dari mereka.
Salah satu pendekatan yang mungkin tidak waktu yang optimal jika order anda kebetulan (atau mencoba) kunjungi sub-sub permasalahan yang belum optimal, khususnya jika ada lebih dari satu cara untuk menghitung subproblem (biasanya caching akan menyelesaikan ini, tapi itu's secara teoritis mungkin bahwa caching tidak mungkin dalam beberapa kasus eksotis). Memoization biasanya akan menambahkan pada waktu kompleksitas untuk ruang anda-kompleksitas (misalnya dengan tabulasi anda memiliki lebih banyak kebebasan untuk membuang perhitungan, seperti menggunakan tabulasi dengan Fib memungkinkan anda untuk menggunakan ruang O(1), tetapi memoization dengan Fib menggunakan O(N) ruang stack).
Di sini kita daftar contoh kepentingan tertentu, yang bukan hanya general DP masalah, tapi menariknya membedakan memoization dan tabulasi. Misalnya, salah satu formulasi mungkin akan jauh lebih mudah dari yang lain, atau mungkin ada optimasi yang pada dasarnya memerlukan tabulasi:
Top down dan bottom up DP adalah dua cara yang berbeda untuk memecahkan masalah yang sama. Mempertimbangkan memoized (top down) vs dinamis (bottom up) solusi pemrograman untuk komputasi angka fibonacci.
fib_cache = {}
def memo_fib(n):
global fib_cache
if n == 0 or n == 1:
return 1
if n in fib_cache:
return fib_cache[n]
ret = memo_fib(n - 1) + memo_fib(n - 2)
fib_cache[n] = ret
return ret
def dp_fib(n):
partial_answers = [1, 1]
while len(partial_answers) <= n:
partial_answers.append(partial_answers[-1] + partial_answers[-2])
return partial_answers[n]
print memo_fib(5), dp_fib(5)
Saya pribadi menemukan memoization jauh lebih alami. Anda dapat mengambil sebuah fungsi rekursif dan memoize dengan proses mekanis (pertama pencarian jawaban di cache dan mengembalikannya jika memungkinkan, jika tidak menghitung secara rekursif dan kemudian sebelum kembali, anda menyimpan perhitungan di cache untuk penggunaan masa depan), sedangkan melakukan bottom up pemrograman dinamis mengharuskan anda untuk menyandikan perintah di mana solusi yang dihitung, sehingga tidak ada "masalah" dihitung sebelum masalah yang lebih kecil bahwa hal itu tergantung pada.
Fitur utama dari pemrograman dinamis adalah adanya sub-sub permasalahan tumpang tindih. Artinya, masalah yang anda mencoba untuk memecahkan dapat dipecah menjadi sub-sub permasalahan, dan banyak dari orang-orang sub-sub permasalahan berbagi subsubproblems. Hal ini seperti "Membagi dan menaklukkan", tetapi anda akhirnya melakukan hal yang sama berkali-kali. Contoh yang saya telah digunakan sejak tahun 2003 ketika mengajar atau menjelaskan hal ini: anda dapat menghitung angka Fibonacci secara rekursif.
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
Menggunakan bahasa favorit anda dan mencoba berjalan untuk fib(50)
. Itu akan memakan waktu yang sangat, sangat lama. Kira-kira waktu sebanyak fib(50)
itu sendiri! Namun, banyak pekerjaan yang tidak perlu dilakukan. fib(50)
akan memanggil fib(49)
dan fib(48)
, tapi kemudian keduanya akan berakhir memanggil fib(47)
, meskipun nilai yang sama. Pada kenyataannya, fib(47)
akan dihitung tiga kali: dengan panggilan langsung dari fib(49)
, dengan panggilan langsung dari fib(48)
, dan juga dengan panggilan langsung dari yang lain fib(48)
, salah satu yang diawali dengan perhitungan fib(49)
... Jadi anda lihat, kita punya sub-sub permasalahan tumpang tindih.
Berita bagus: ada tidak perlu untuk menghitung nilai yang sama berkali-kali. Setelah anda menghitung sekali, cache hasil, dan menggunakan waktu berikutnya cache value! Ini adalah inti dari pemrograman dinamis. Anda dapat menyebutnya "top-down", "memoization", atau apa pun yang anda inginkan. Pendekatan ini sangat intuitif dan sangat mudah untuk menerapkan. Hanya menulis rekursif solusi pertama, tes pada tes kecil, tambahkan memoization (caching sudah dihitung nilai-nilai), dan --- bingo! --- anda selesai.
Biasanya anda juga dapat menulis setara iteratif program yang bekerja dari bawah ke atas, tanpa rekursi. Dalam hal ini ini akan menjadi pendekatan yang lebih alami: loop dari 1 sampai 50 komputasi semua angka-angka Fibonacci sebagai anda pergi.
fib[0] = 0
fib[1] = 1
for i in range(48):
fib[i+2] = fib[i] + fib[i+1]
Dalam setiap skenario yang menarik bottom-up solusi ini biasanya lebih sulit untuk mengerti. Namun, setelah anda memahami hal ini, biasanya anda'd mendapatkan jauh lebih jelas gambaran besar tentang bagaimana algoritma ini bekerja. Dalam prakteknya, ketika pemecahan trivial masalah, saya sarankan pertama yang menulis pendekatan top-down dan pengujian pada contoh. Kemudian menulis bottom-up solusi dan membandingkan dua untuk memastikan anda mendapatkan hal yang sama. Idealnya, membandingkan dua solusi secara otomatis. Menulis kecil rutin yang akan menghasilkan banyak tes, idealnya -- semua tes kecil hingga ukuran tertentu --- dan memvalidasi bahwa kedua solusi memberikan hasil yang sama. Setelah itu gunakan bottom-up solusi dalam produksi, tapi tetap atas-bawah, kode, komentar. Ini akan membuat lebih mudah bagi pengembang lain untuk memahami apa yang anda lakukan: bottom-up kode bisa sangat dimengerti, bahkan anda menulis dan bahkan jika anda tahu persis apa yang anda lakukan.
Dalam banyak aplikasi dengan pendekatan bottom-up adalah sedikit lebih cepat karena overhead dari pemanggilan rekursif. Stack overflow juga dapat menjadi masalah dalam masalah-masalah tertentu, dan perhatikan bahwa ini dapat sangat tergantung pada input data. Dalam beberapa kasus, anda mungkin tidak dapat untuk menulis tes menyebabkan stack overflow jika anda don't memahami pemrograman dinamis cukup baik, tapi beberapa hari ini mungkin masih terjadi.
Sekarang, ada masalah di mana pendekatan top-down adalah satu-satunya solusi yang layak karena masalah tata ruang ini begitu besar bahwa hal ini tidak mungkin untuk memecahkan semua sub-sub permasalahan. Namun, "caching" masih bekerja dalam waktu yang wajar karena masukan anda hanya membutuhkan sebagian kecil dari sub-sub permasalahan yang harus dipecahkan --- tapi itu terlalu sulit untuk secara jelas mendefinisikan, sub-sub permasalahan yang anda butuhkan untuk memecahkan, dan karenanya untuk menulis bottom-up solusi. Di sisi lain, ada situasi ketika anda tahu anda akan perlu untuk memecahkan semua sub-sub permasalahan. Dalam hal ini pergi dan menggunakan bottom-up.
Saya pribadi akan menggunakan atas-bawah untuk Ayat optimasi.k.a Word wrap masalah optimasi (lihat Knuth-Plass line-melanggar algoritma; setidaknya TeX menggunakan itu, dan beberapa perangkat lunak oleh Adobe Systems menggunakan pendekatan yang sama). Saya akan menggunakan bottom-up untuk Fast Fourier Transform.
Mari kita fibonacci series sebagai contoh
1,1,2,3,5,8,13,21....
first number: 1
Second number: 1
Third Number: 2
Cara lain untuk menempatkan,
Bottom(first) number: 1
Top (Eighth) number on the given sequence: 21
Dalam kasus pertama lima bilangan fibonacci
Bottom(first) number :1
Top (fifth) number: 5
Sekarang mari kita lihat dari rekursif Fibonacci series algoritma sebagai contoh
public int rcursive(int n) {
if ((n == 1) || (n == 2)) {
return 1;
} else {
return rcursive(n - 1) + rcursive(n - 2);
}
}
Sekarang jika kita menjalankan program ini dengan perintah berikut
rcursive(5);
jika kita cermat melihat ke dalam algoritma, dalam rangka untuk menghasilkan kelima nomor memerlukan 3 dan 4 angka. Jadi saya rekursi benar-benar mulai dari atas(5) dan kemudian pergi semua jalan ke bawah/lebih rendah angka. Pendekatan ini sebenarnya adalah pendekatan top-down.
Untuk menghindari melakukan perhitungan yang sama beberapa kali kami menggunakan teknik Pemrograman Dinamis. Kita simpan sebelumnya dihitung nilai dan menggunakannya kembali. Teknik ini disebut memoization. Ada lebih untuk pemrograman Dinamis lainnya kemudian memoization yang tidak diperlukan untuk membahas masalah saat ini.
Top-Down
Mari kita menulis ulang algoritma asli dan menambahkan memoized teknik.
public int memoized(int n, int[] memo) {
if (n <= 2) {
return 1;
} else if (memo[n] != -1) {
return memo[n];
} else {
memo[n] = memoized(n - 1, memo) + memoized(n - 2, memo);
}
return memo[n];
}
Dan kita menjalankan metode ini seperti berikut
int n = 5;
int[] memo = new int[n + 1];
Arrays.fill(memo, -1);
memoized(n, memo);
Solusi ini masih top-down sebagai algoritma mulai dari atas nilai dan pergi ke bawah setiap langkah untuk mendapatkan nilai tertinggi.
Bottom-Up
Tapi, pertanyaannya adalah, bisakah kita mulai dari bawah, seperti dari pertama bilangan fibonacci kemudian berjalan dengan cara kami untuk naik. Mari kita menulis ulang dengan menggunakan teknik ini,
public int dp(int n) {
int[] output = new int[n + 1];
output[1] = 1;
output[2] = 1;
for (int i = 3; i <= n; i++) {
output[i] = output[i - 1] + output[i - 2];
}
return output[n];
}
Sekarang jika kita melihat ke dalam algoritma ini sebenarnya mulai dari nilai yang lebih rendah kemudian pergi ke atas. Jika saya perlu 5 bilangan fibonacci saya benar-benar menghitung 1, maka kedua kemudian ketiga sepanjang jalan untuk sampai nomor 5. Teknik ini sebenarnya disebut bottom-up teknik.
Dua yang terakhir, algoritma penuh mengisi pemrograman dinamis persyaratan. Tapi salah satunya adalah top-down dan bottom-up. Kedua algoritma ini memiliki ruang yang sama dan kompleksitas waktu.
Pemrograman dinamis ini sering disebut Memoization!
1.Memoization adalah top-down teknik(mulai memecahkan masalah yang diberikan oleh memecahnya) dan dynamic programming adalah teknik bottom-up(mulai memecahkan dari yang sepele sub-masalah, up terhadap masalah yang diberikan)
2.DP menemukan solusi dengan memulai dari kasus dasar(s) dan bekerja dengan cara ke atas. DP memecahkan semua sub-masalah, karena itu tidak secara bottom-up
tidak Seperti Memoization, yang memecahkan hanya diperlukan sub-sub masalah
DP memiliki potensi untuk mengubah eksponensial-waktu brute-force solusi ke polynomial-time algoritma.
DP mungkin akan jauh lebih efisien karena berulang
sebaliknya, Memoization harus membayar (sering signifikan) overhead karena rekursi.
Untuk lebih sederhana, Memoization menggunakan pendekatan top-down untuk memecahkan masalah yaitu dimulai dengan inti(utama) masalah kemudian mengelompokkannya ke dalam sub-sub masalah dan menyelesaikan sub-masalah yang sama. Dalam pendekatan ini sama sub-masalah yang dapat terjadi beberapa kali dan mengkonsumsi lebih banyak CPU siklus, oleh karena itu meningkatkan kompleksitas waktu. Sedangkan pada pemrograman Dinamis yang sama sub-masalah tidak akan bisa diselesaikan beberapa kali tapi sebelum hasilnya akan digunakan untuk mengoptimalkan solusi.
Berikut adalah DP yang berbasis solusi untuk Mengedit masalah Jarak yang bersifat top down. Saya berharap ini juga akan membantu dalam memahami dunia Pemrograman Dinamis:
public int minDistance(String word1, String word2) {//Standard dynamic programming puzzle.
int m = word2.length();
int n = word1.length();
if(m == 0) // Cannot miss the corner cases !
return n;
if(n == 0)
return m;
int[][] DP = new int[n + 1][m + 1];
for(int j =1 ; j <= m; j++) {
DP[0][j] = j;
}
for(int i =1 ; i <= n; i++) {
DP[i][0] = i;
}
for(int i =1 ; i <= n; i++) {
for(int j =1 ; j <= m; j++) {
if(word1.charAt(i - 1) == word2.charAt(j - 1))
DP[i][j] = DP[i-1][j-1];
else
DP[i][j] = Math.min(Math.min(DP[i-1][j], DP[i][j-1]), DP[i-1][j-1]) + 1; // Main idea is this.
}
}
return DP[n][m];
}
Yang dapat anda pikirkan yang rekursif pelaksanaan di rumah anda. It's cukup baik dan menantang jika anda belum't diselesaikan sesuatu seperti ini sebelumnya.