Apa perbedaan antara bottom-up dan top-down?

177

Pendekatan bottom-up (untuk pemrograman dinamis) terdiri dari pertama-tama melihat subproblem "lebih kecil", dan kemudian menyelesaikan subproblem yang lebih besar menggunakan solusi untuk masalah yang lebih kecil.

The top-down terdiri dalam memecahkan masalah dalam "cara alami" dan centang jika Anda telah menghitung solusi untuk subproblem sebelumnya.

Saya agak bingung. Apa perbedaan antara keduanya?

Tamu
sumber

Jawaban:

247

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 fibfungsi 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
ninjagecko
sumber
3
@ coder000001: untuk contoh python, Anda dapat mencari google python memoization decorator; beberapa bahasa akan memungkinkan Anda menulis makro atau kode yang merangkum pola memoisasi. Pola memoisasi tidak lebih dari "daripada memanggil fungsi, cari nilai dari cache (jika nilainya tidak ada, hitung dan tambahkan ke cache terlebih dahulu)".
ninjagecko
16
Saya tidak melihat ada yang menyebutkan ini, tetapi saya pikir keuntungan lain dari Top down adalah Anda hanya akan membuat tabel / cache pencarian jarang. (yaitu Anda mengisi nilai-nilai di mana Anda benar-benar membutuhkannya). Jadi ini mungkin pro dan coding mudah. Dengan kata lain, top down mungkin menghemat waktu berlari Anda yang sebenarnya karena Anda tidak menghitung semuanya (Anda mungkin memiliki waktu berlari yang jauh lebih baik tetapi waktu berlari asimtotik yang sama). Namun itu membutuhkan memori tambahan untuk menjaga frame stack tambahan (sekali lagi, konsumsi memori 'mungkin' (hanya mungkin) berlipat ganda tetapi asimtotiknya sama.
InformedA
2
Saya mendapat kesan bahwa pendekatan top-down bahwa solusi cache untuk subproblem yang tumpang tindih adalah teknik yang disebut memoisasi . Teknik bottom up yang mengisi tabel dan juga menghindari rekomputasi subproblem yang tumpang tindih disebut sebagai tabulasi . Teknik-teknik ini dapat digunakan saat menggunakan pemrograman dinamis , yang mengacu pada penyelesaian sub-masalah untuk menyelesaikan masalah yang jauh lebih besar. Ini sepertinya bertentangan dengan jawaban ini, di mana jawaban ini menggunakan pemrograman dinamis alih-alih tabulasi di banyak tempat. Siapa yang benar
Sammaron
1
@Sammaron: hmm, Anda membuat poin yang bagus. Mungkin seharusnya saya memeriksa sumber saya di Wikipedia, yang tidak dapat saya temukan. Setelah memeriksa cstheory.stackexchange sedikit, saya sekarang setuju "bottom-up" akan menyiratkan bagian bawah diketahui sebelumnya (tabulasi), dan "top-down" adalah Anda menganggap solusi untuk subproblem / subpohon. Pada saat itu saya menemukan istilah yang ambigu, dan saya menafsirkan frasa dalam tampilan ganda ("bottom-up" Anda menganggap solusi untuk subproblem dan menghafal, "top-down" Anda tahu subproblem Anda tentang dan dapat melakukan tabulasi). Saya akan berusaha untuk mengatasi ini dalam suntingan.
ninjagecko
1
@mgiuffrida: Stack space terkadang diperlakukan berbeda tergantung pada bahasa pemrograman. Misalnya dalam python, mencoba melakukan memo rekursif yang gagal akan gagal katakan fib(513). Terminologi kelebihan beban yang saya rasakan menghambat di sini. 1) Anda selalu dapat membuang sub-masalah yang tidak lagi Anda perlukan. 2) Anda selalu dapat menghindari penghitungan masalah yang tidak Anda butuhkan. 3) 1 dan 2 mungkin jauh lebih sulit untuk dikodekan tanpa struktur data eksplisit untuk menyimpan subproblem, ATAU, lebih sulit jika aliran kontrol harus menenun antar panggilan fungsi (Anda mungkin perlu status atau kelanjutan).
ninjagecko
76

DP top-down dan bottom-up adalah dua cara berbeda untuk menyelesaikan masalah yang sama. Pertimbangkan solusi pemrograman memoized (atas ke bawah) vs dinamis (bawah ke atas) untuk menghitung 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 memoisasi jauh lebih alami. Anda dapat mengambil fungsi rekursif dan memoize itu dengan proses mekanis (pertama mencari jawaban dalam cache dan mengembalikannya jika memungkinkan, jika tidak menghitungnya secara rekursif dan kemudian sebelum kembali, Anda menyimpan perhitungan dalam cache untuk digunakan di masa mendatang), sedangkan melakukan bottom up pemrograman dinamis mengharuskan Anda untuk menyandikan urutan solusi yang dihitung, sehingga tidak ada "masalah besar" yang dihitung sebelum masalah yang lebih kecil yang bergantung padanya.

Rob Neuhaus
sumber
1
Ah, sekarang saya mengerti apa arti "top-down" dan "bottom-up"; itu sebenarnya hanya mengacu pada memoisasi vs DP. Dan untuk berpikir saya adalah orang yang mengedit pertanyaan untuk menyebutkan DP dalam judul ...
ninjagecko
apa runtime dari fibized rekursif fib v / s normal?
Siddhartha
eksponensial (2 ^ n) untuk normal karena pohon rekursi saya pikir.
Siddhartha
1
Ya itu linear! Saya menarik keluar pohon rekursi dan melihat panggilan apa yang bisa dihindari dan menyadari memo_fib (n - 2) semua akan dihindari setelah panggilan pertama ke sana, sehingga semua cabang yang tepat dari pohon rekursi akan terputus dan akan mengurangi menjadi linear.
Siddhartha
1
Karena DP pada dasarnya melibatkan membangun tabel hasil di mana setiap hasil dihitung paling banyak sekali, salah satu cara sederhana untuk memvisualisasikan runtime algoritma DP adalah untuk melihat seberapa besar tabel tersebut. Dalam hal ini, ukurannya n (satu hasil per nilai input) jadi O (n). Dalam kasus lain, ini bisa berupa matriks n ^ 2, menghasilkan O (n ^ 2), dll.
Johnson Wong
22

Fitur utama dari pemrograman dinamis adalah adanya subproblem yang tumpang tindih . Yaitu, masalah yang Anda coba selesaikan dapat dipecah menjadi subproblem, dan banyak dari subproblem tersebut berbagi sub-masalah. Itu seperti "Membagi dan menaklukkan", tetapi Anda akhirnya melakukan hal yang sama berkali-kali. Contoh yang saya gunakan sejak 2003 ketika mengajar atau menjelaskan masalah ini: Anda dapat menghitung angka Fibonacci secara rekursif.

def fib(n):
  if n < 2:
    return n
  return fib(n-1) + fib(n-2)

Gunakan bahasa favorit Anda dan coba jalankan untuk fib(50). Ini akan memakan waktu yang sangat, sangat lama. Kira-kira sebanyak waktu fib(50)itu sendiri! Namun, banyak pekerjaan yang tidak perlu sedang dilakukan. fib(50)akan memanggil fib(49)dan fib(48), tetapi kemudian keduanya akan berakhir memanggil fib(47), meskipun nilainya sama. Bahkan, 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 dihasilkan oleh perhitungan fib(49)... Jadi Anda lihat, kami memiliki subproblem yang tumpang tindih .

Berita bagus: tidak perlu menghitung nilai yang sama berkali-kali. Setelah Anda menghitungnya sekali, simpan hasilnya, dan lain kali gunakan nilai yang di-cache! 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 diimplementasikan. Cukup tulis solusi rekursif terlebih dahulu, ujilah pada tes kecil, tambahkan memoisasi (caching dari nilai yang sudah dihitung), dan --- bingo! --- kamu selesai.

Biasanya Anda juga dapat menulis program iteratif setara yang bekerja dari bawah ke atas, tanpa rekursi. Dalam hal ini ini akan menjadi pendekatan yang lebih alami: loop dari 1 hingga 50 menghitung semua angka Fibonacci saat Anda pergi.

fib[0] = 0
fib[1] = 1
for i in range(48):
  fib[i+2] = fib[i] + fib[i+1]

Dalam skenario yang menarik, solusi dari bawah ke atas biasanya lebih sulit untuk dipahami. Namun, begitu Anda memahaminya, biasanya Anda akan mendapatkan gambaran besar yang lebih jelas tentang bagaimana algoritma bekerja. Dalam praktiknya, ketika memecahkan masalah nontrivial, saya sarankan terlebih dahulu menulis pendekatan top-down dan mengujinya pada contoh-contoh kecil. Kemudian tulis solusi bottom-up dan bandingkan keduanya untuk memastikan Anda mendapatkan hal yang sama. Idealnya, bandingkan dua solusi secara otomatis. Tulis rutin kecil yang akan menghasilkan banyak tes, idealnya - semuanyates kecil hingga ukuran tertentu --- dan memvalidasi bahwa kedua solusi memberikan hasil yang sama. Setelah itu gunakan solusi bottom-up dalam produksi, tetapi simpan kode top-bottom, berkomentar. Ini akan memudahkan pengembang lain untuk memahami apa yang Anda lakukan: kode bottom-up bisa sangat tidak bisa dimengerti, bahkan Anda yang menulisnya dan bahkan jika Anda tahu persis apa yang Anda lakukan.

Dalam banyak aplikasi pendekatan bottom-up sedikit lebih cepat karena overhead panggilan rekursif. Stack overflow juga bisa menjadi masalah dalam masalah-masalah tertentu, dan perhatikan bahwa ini sangat bergantung pada input data. Dalam beberapa kasus Anda mungkin tidak dapat menulis tes yang menyebabkan stack overflow jika Anda tidak memahami pemrograman dinamis dengan baik, tetapi suatu hari ini mungkin masih terjadi.

Sekarang, ada masalah di mana pendekatan top-down adalah satu-satunya solusi yang layak karena ruang masalah sangat besar sehingga tidak mungkin untuk menyelesaikan semua submasalah. Namun, "caching" masih berfungsi dalam waktu yang wajar karena input Anda hanya membutuhkan sebagian kecil dari subproblem yang harus dipecahkan --- tetapi terlalu sulit untuk secara eksplisit mendefinisikan, subproblem mana yang perlu Anda selesaikan, dan karenanya menulis bottom- solusi atas. Di sisi lain, ada situasi di mana Anda tahu Anda harus menyelesaikan semua submasalah. Dalam hal ini, lanjutkan dan gunakan bottom-up.

Saya pribadi akan menggunakan atas-bawah untuk optimasi Paragraf alias masalah optimasi bungkus kata (lihat algoritma pemecah garis Knuth-Plass; setidaknya TeX menggunakannya, dan beberapa perangkat lunak oleh Adobe Systems menggunakan pendekatan yang sama). Saya akan menggunakan bottom-up untuk Fast Fourier Transform .

osa
sumber
Halo!!! Saya ingin menentukan apakah proposisi berikut ini benar. - Untuk algoritma Pemrograman Dinamis, perhitungan semua nilai dengan bottom-up secara asimptotik lebih cepat daripada penggunaan rekursi dan memoisasi. - Waktu algoritma dinamis selalu Ο (Ρ) di mana Ρ adalah jumlah sub-masalah. - Setiap masalah dalam NP dapat diselesaikan dalam waktu eksponensial.
Mary Star
Apa yang bisa saya katakan tentang proposisi di atas? Anda punya ide? @osa
Mary Star
@ evinda, (1) selalu salah. Ini bisa sama atau lebih lambat asimptotik (ketika Anda tidak membutuhkan semua submasalah, rekursi bisa lebih cepat). (2) hanya benar jika Anda dapat menyelesaikan setiap sub-masalah dalam O (1). (3) agak benar. Setiap masalah dalam NP dapat diselesaikan dalam waktu polinomial pada mesin nondeterministic (seperti komputer kuantum, yang dapat melakukan banyak hal secara bersamaan: memiliki kue, dan sekaligus memakannya, dan melacak kedua hasil). Jadi dalam arti tertentu, setiap masalah dalam NP dapat diselesaikan dalam waktu eksponensial pada komputer biasa. Catatan: semua dalam P juga dalam NP. Misalnya menambahkan dua bilangan bulat
osa
19

Mari kita ambil seri fibonacci sebagai contoh

1,1,2,3,5,8,13,21....

first number: 1
Second number: 1
Third Number: 2

Cara lain untuk mengatakannya,

Bottom(first) number: 1
Top (Eighth) number on the given sequence: 21

Dalam kasus lima nomor fibonacci pertama

Bottom(first) number :1
Top (fifth) number: 5 

Sekarang mari kita lihat algoritma seri Fibonacci rekursif 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 melihat dari dekat ke algoritma, untuk menghasilkan angka kelima diperlukan angka ke-3 dan ke-4. Jadi rekursi saya sebenarnya dimulai dari atas (5) dan kemudian berlanjut sampai ke angka bawah / bawah. Pendekatan ini sebenarnya adalah pendekatan top-down.

Untuk menghindari melakukan perhitungan yang sama beberapa kali, kami menggunakan teknik Pemrograman Dinamis. Kami menyimpan nilai yang dihitung sebelumnya dan menggunakannya kembali. Teknik ini disebut memoisasi. Ada lebih banyak untuk pemrograman dinamis selain memoisasi yang tidak diperlukan untuk membahas masalah saat ini.

Perintahkan ke bawah

Mari kita menulis ulang algoritma asli kita dan menambahkan teknik memoised.

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 kami 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 nilai teratas dan pergi ke bawah setiap langkah untuk mendapatkan nilai teratas kami.

Bawah ke Atas

Tapi, pertanyaannya adalah, bisakah kita mulai dari bawah, seperti dari nomor fibonacci pertama lalu berjalan ke atas. Mari kita menulis ulang 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 algoritma ini sebenarnya dimulai dari nilai yang lebih rendah kemudian pergi ke atas. Jika saya membutuhkan nomor 5 fibonacci saya benar-benar menghitung 1, kemudian kedua lalu ketiga sampai ke nomor 5. Teknik ini sebenarnya disebut teknik bottom-up.

Terakhir dua, algoritma memenuhi persyaratan pemrograman dinamis. Tapi satu dari atas ke bawah dan satu lagi dari bawah ke atas. Kedua algoritma memiliki kompleksitas ruang dan waktu yang serupa.

minhaz
sumber
Bisakah kita mengatakan pendekatan bottom-up sering diterapkan dengan cara yang tidak rekursif?
Lewis Chan
Tidak, Anda dapat mengubah logika loop apa pun menjadi rekursi
Ashvin Sharma
3

Pemrograman Dinamis sering disebut Memoisasi!

1.Memoisasi adalah teknik top-down (mulai memecahkan masalah yang diberikan dengan memecahnya) dan pemrograman dinamis adalah teknik bottom-up (mulai pemecahan dari sub-masalah sepele, hingga masalah yang diberikan)

2.DP menemukan solusinya dengan mulai dari kasus dasar dan bekerja ke atas. DP menyelesaikan semua sub-masalah, karena ia melakukan bottom-up

Tidak seperti Memoization, yang hanya memecahkan sub-masalah yang dibutuhkan

  1. DP memiliki potensi untuk mengubah solusi brute-force waktu eksponensial menjadi algoritma waktu polinomial.

  2. DP mungkin jauh lebih efisien karena sifatnya yang berulang

Sebaliknya, Memoisasi harus membayar biaya overhead (sering signifikan) karena rekursi.

Agar lebih sederhana, Memoization menggunakan pendekatan top-down untuk memecahkan masalah yaitu mulai dengan masalah inti (utama) kemudian memecahnya menjadi sub-masalah dan menyelesaikan sub-masalah ini dengan cara yang sama. Dalam pendekatan ini sub-masalah yang sama dapat terjadi beberapa kali dan mengkonsumsi lebih banyak siklus CPU, karenanya menambah kompleksitas waktu. Sedangkan dalam pemrograman dinamis, sub-masalah yang sama tidak akan diselesaikan beberapa kali tetapi hasil sebelumnya akan digunakan untuk mengoptimalkan solusi.

Farah Nazifa
sumber
4
itu tidak benar, memoisasi menggunakan cache yang akan membantu Anda menghemat kompleksitas waktu hingga sama dengan DP
InformedA
3

Cukup mengatakan pendekatan top-down menggunakan rekursi untuk memanggil masalah Sub berulang-ulang di
mana sebagai pendekatan bottom-up menggunakan single tanpa memanggil salah satu dan karenanya lebih efisien.


sumber
1

Berikut ini adalah solusi berbasis DP untuk masalah Edit Distance yang top-down. Saya harap ini juga akan membantu dalam memahami dunia Programming 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];
}

Anda dapat memikirkan penerapannya secara rekursif di rumah Anda. Ini cukup bagus dan menantang jika Anda belum menyelesaikan sesuatu seperti ini sebelumnya.

piyush121
sumber
1

Top-Down : Melacak nilai yang dihitung sampai sekarang dan mengembalikan hasilnya ketika kondisi dasar terpenuhi.

int n = 5;
fibTopDown(1, 1, 2, n);

private int fibTopDown(int i, int j, int count, int n) {
    if (count > n) return 1;
    if (count == n) return i + j;
    return fibTopDown(j, i + j, count + 1, n);
}

Bottom-Up : Hasil saat ini tergantung pada hasil dari sub-masalah.

int n = 5;
fibBottomUp(n);

private int fibBottomUp(int n) {
    if (n <= 1) return 1;
    return fibBottomUp(n - 1) + fibBottomUp(n - 2);
}
Ashwin
sumber