Tentang apa pemrograman dinamis?


33

Maaf sebelumnya jika pertanyaan ini terdengar bodoh ...

Sejauh yang saya tahu, membangun algoritma menggunakan pemrograman dinamis bekerja seperti ini:

  1. mengungkapkan masalah sebagai relasi berulang;
  2. menerapkan hubungan perulangan baik melalui memoisasi atau melalui pendekatan bottom up.

Sejauh yang saya tahu, saya telah mengatakan segalanya tentang pemrograman dinamis. Maksud saya: pemrograman dinamis tidak memberikan alat / aturan / metode / teorema untuk mengekspresikan hubungan perulangan, atau untuk mengubahnya menjadi kode.

Jadi, apa yang spesial dari pemrograman dinamis? Apa yang diberikannya kepada Anda, selain metode yang tidak jelas untuk mendekati jenis masalah tertentu?


11
Factoid historis (komentar ini tidak akan membantu Anda, tetapi Bellman sebenarnya adalah pemimpin yang baik jika Anda ingin mendapatkan teori yang berat tentang pemrograman dinamis): ketika Bellman datang dengan apa yang sekarang dikenal sebagai pemrograman dinamis, ia menyebut ide "pemrograman dinamis "Karena pekerjaan teoretis murni tidak akan terbang dengan atasannya pada waktu itu, jadi dia membutuhkan sesuatu yang lebih banyak kata-kata yang tidak dapat digunakan dengan cara yang merendahkan .
G. Bach

3
Sejauh yang saya tahu persis inilah dua poin yang Anda sebutkan. Itu menjadi istimewa ketika menghindari ledakan eksponensial karena subproblem yang tumpang tindih. Itu saja. Ah, omong-omong, profesor saya lebih suka "paradigma algoritmik" daripada "metode kabur".
Hendrik Jan

"Pemrograman dinamis" tampaknya terutama merupakan kata kunci (sejak itu kehilangan kata kunci) Itu tidak berarti itu tidak berguna tentunya.
user253751

3
Tidak layak mendapat jawaban, tetapi bagi saya pemrograman dinamis jelas merupakan "hal yang Anda gunakan ketika Anda mencoba memecahkan masalah secara rekursif, tetapi Anda akhirnya membuang-buang waktu untuk meninjau kembali subproblem yang sama berulang-ulang."
hobbs

@ hobbs: Tepat, tetapi keterampilannya adalah menemukan cara awal membuang-buang waktu;)
j_random_hacker

Jawaban:


27

Pemrograman dinamis memberi Anda cara untuk berpikir tentang desain algoritma. Ini seringkali sangat membantu.

Metode memoo dan bottom-up memberi Anda aturan / metode untuk mengubah hubungan perulangan menjadi kode. Memoisasi adalah ide yang relatif sederhana, tetapi sering kali ide terbaik adalah!

Pemrograman dinamis memberi Anda cara terstruktur untuk berpikir tentang waktu berjalan dari algoritma Anda. Waktu yang berjalan pada dasarnya ditentukan oleh dua angka: jumlah sub-masalah yang harus Anda selesaikan, dan waktu yang dibutuhkan untuk menyelesaikan setiap sub-masalah. Ini memberikan cara mudah yang mudah untuk memikirkan masalah desain algoritma. Ketika Anda memiliki relasi pengulangan kandidat, Anda dapat melihatnya dan dengan sangat cepat mengetahui waktu pelaksanaannya (misalnya, Anda seringkali dapat dengan cepat memberi tahu berapa banyak subproblem yang akan ada, yang merupakan batas bawah pada waktu berjalan; jika ada banyak masalah yang harus Anda selesaikan secara eksponensial, maka perulangan mungkin tidak akan menjadi pendekatan yang baik). Ini juga membantu Anda mengesampingkan dekomposisi kandidat subproblem. Misalnya, jika kita memiliki string urutan S tidak mungkin menjadi pendekatan yang baik (jumlah subproblem adalah eksponensial dalam n ). Ini memungkinkan Anda memangkas "ruang pencarian" dari kemungkinan perulangan. , mendefinisikan subproblem dengan awalan S [ 1 .. i ] atau suffix S [ j . . n ] atau substring S [ i . . j ] mungkin masuk akal (jumlah subproblem adalah polinomial dalam n ), tetapi mendefinisikan subproblem dengan urutan dariS[1..n]S[1..i]S[j..n]S[i..j]nSn

Pemrograman dinamis memberi Anda pendekatan terstruktur untuk mencari hubungan pengulangan kandidat. Secara empiris, pendekatan ini seringkali efektif. Secara khusus, ada beberapa heuristik / pola umum yang dapat Anda kenali untuk cara-cara umum untuk mendefinisikan subproblem, tergantung pada jenis input. Contohnya:

  • Jika input adalah bilangan bulat positif , salah satu cara kandidat untuk mendefinisikan subproblem adalah dengan mengganti n dengan integer yang lebih kecil n (st 0 n nnn ).0nn

  • Jika inputnya adalah string , beberapa kandidat cara untuk mendefinisikan subproblem meliputi: ganti S [ 1 .. n ] dengan awalan S [ 1 .. i ] ; ganti S [ 1 .. n ] dengan akhiran S [ j . . n ] ; ganti S [ 1 .. n ] dengan substring S [ i . (Di sini subproblem ditentukan oleh pilihan i ,S[1..n]S[1..n]S[1..i]S[1..n]S[j..n]S[1..n]S[i..j]i,j .)

  • Jika input adalah daftar , lakukan hal yang sama seperti yang Anda lakukan untuk string.

  • Jika inputnya adalah pohon , salah satu cara kandidat untuk mendefinisikan subproblem adalah mengganti T dengan subtree T (yaitu, pilih node x dan ganti T dengan subtree yang di-root pada x ; subproblem ditentukan oleh pilihan x ).TTTxTxx

  • Jika inputnya adalah sepasang , maka secara rekursif lihatlah tipe x dan tipe y untuk mengidentifikasi cara untuk memilih subproblem untuk masing-masing. Dengan kata lain, satu kandidat cara untuk mendefinisikan subproblem adalah mengganti ( x , y ) dengan ( x , y ) di mana x adalah subproblem untuk x dan y adalah subproblem untuk y . (Anda juga dapat mempertimbangkan submasalah formulir(x,y)xy(x,y)(x,y)xxyy Atau ( x , y ) .)(x,y)(x,y)

Dan seterusnya. Ini memberi Anda heuristik yang sangat berguna: hanya dengan melihat tipe tanda tangan dari metode ini, Anda dapat menemukan daftar cara kandidat untuk mendefinisikan subproblem. Dengan kata lain, hanya dengan melihat pernyataan masalah - hanya melihat jenis input - Anda dapat menemukan beberapa kandidat cara untuk mendefinisikan subproblem.

Ini seringkali sangat membantu. Itu tidak memberi tahu Anda apa hubungan perulangan itu, tetapi ketika Anda memiliki pilihan tertentu untuk bagaimana mendefinisikan subproblem, seringkali tidak terlalu sulit untuk menentukan hubungan perulangan yang sesuai. Jadi, sering kali mengubah desain algoritma pemrograman dinamis menjadi pengalaman terstruktur. Anda menuliskan pada kertas memo daftar kandidat cara untuk mendefinisikan subproblem (menggunakan heuristik di atas). Kemudian, untuk masing-masing kandidat, Anda mencoba untuk menulis hubungan pengulangan, dan mengevaluasi waktu berjalannya dengan menghitung jumlah sub-masalah dan waktu yang dihabiskan per sub-masalah. Setelah mencoba setiap kandidat, Anda menyimpan yang terbaik yang dapat Anda temukan. Menyediakan beberapa struktur untuk proses desain algoritma adalah bantuan besar, karena jika tidak, desain algoritma dapat mengintimidasi (ada '


Jadi Anda mengonfirmasi bahwa pemrograman dinamis tidak menyediakan "prosedur" konkret untuk diikuti. Itu hanya "cara berpikir", seperti yang Anda katakan. Perhatikan bahwa saya tidak berpendapat bahwa DP tidak berguna (sebaliknya!), Saya hanya mencoba memahami jika ada sesuatu yang saya lewatkan atau jika saya hanya harus berlatih lebih banyak.
hei hei

@heyhey, yah, ya ... dan tidak. Lihat jawaban saya yang sudah direvisi untuk penjelasan lebih lanjut. Ini bukan peluru perak, tetapi memberikan beberapa prosedur semi-konkret yang sering membantu (tidak dijamin untuk bekerja, tetapi sering terbukti bermanfaat).
DW

Terimakasih banyak! Dengan berlatih, saya menjadi semakin terbiasa dengan beberapa "prosedur semi-konkret" yang Anda gambarkan.
hei hei

"Jika ada banyak masalah yang harus Anda selesaikan secara eksponensial, maka perulangan mungkin tidak akan menjadi pendekatan yang baik". Untuk banyak masalah tidak ada algoritma waktu polinomial yang dikenal. Mengapa ini harus menjadi kriteria untuk menggunakan DP?
Chiel ten Brinke

@Chiel, ini bukan kriteria untuk menggunakan DP. Jika Anda memiliki masalah di mana Anda akan senang dengan algoritma waktu eksponensial, maka Anda dapat mengabaikan komentar kurung tertentu. Ini hanya sebuah contoh untuk mencoba mengilustrasikan poin umum yang saya buat - bukan sesuatu yang Anda harus anggap terlalu serius atau ditafsirkan sebagai aturan yang keras dan cepat.
DW

9

Pemahaman Anda tentang pemrograman dinamis benar ( afaik ), dan pertanyaan Anda dibenarkan.

Saya pikir ruang desain tambahan yang kita dapatkan dari jenis perulangan yang kita sebut "pemrograman dinamis" paling baik dilihat dibandingkan dengan skema pendekatan rekursif lainnya.

Mari kita berpura-pura input kita adalah array demi menyoroti konsep.A[1..n]

  1. Pendekatan Induktif

    Di sini idenya adalah untuk membuat masalah Anda lebih kecil, pecahkan versi yang lebih kecil dan dapatkan solusi untuk yang asli. Secara skematis,

    f(A)=g(f(A[1..nc]),A)

    dengan fungsi / algoritma yang menerjemahkan solusi.g

    Contoh: Menemukan bintang dalam waktu linier

  2. Bagilah & Taklukkan

    Partisi input menjadi beberapa bagian yang lebih kecil, pecahkan masalah untuk masing-masing dan gabungkan. Secara skematis (untuk dua bagian),

    .f(A)=g(f(A[1..c]),f(A[c+1..n]),A)

    Contoh: Gabung- / Quicksort, Jarak berpasangan terpendek di pesawat

  3. Pemrograman Dinamis

    Pertimbangkan semua cara mempartisi masalah menjadi masalah yang lebih kecil dan pilih yang terbaik. Secara skematis (untuk dua bagian),

    .f(A)=best{g(f(A[1..c]),f(A[c+1..n]))|1cn1}

    Contoh: Edit jarak, Masalah perubahan-pembuatan

    Catatan penting: pemrograman dinamis bukanlah kekerasan ! Penerapan dalam setiap langkah mengurangi ruang pencarian.best

Dalam arti tertentu, Anda tahu semakin sedikit statis berjalan dari atas ke bawah, dan harus membuat semakin banyak keputusan secara dinamis.

Pelajaran dari belajar tentang pemrograman dinamis adalah tidak apa - apa untuk mencoba semua partisi yang mungkin (well, itu diperlukan untuk kebenaran) karena masih bisa efisien menggunakan memoisasi.


"Pruned Dynamic Programming" (ketika itu berlaku) membuktikan bahwa mencoba semua kemungkinan TIDAK diperlukan untuk kebenaran.
Ben Voigt

@ BenVoigt Tentu saja. Saya dengan sengaja tetap kabur tentang apa artinya "semua cara untuk memisah-misahkan"; Anda ingin mengesampingkan sebanyak mungkin, tentu saja! (Namun, bahkan jika Anda mencoba semua cara mempartisi Anda tidak mendapatkan kekuatan kasar karena Anda hanya pernah menyelidiki kombinasi solusi optimal untuk subproblem, sedangkan brute-force akan menyelidiki semua kombinasi semua solusi.)
Raphael


5

Pemrograman Dinamis memungkinkan Anda untuk menukar memori dengan waktu komputasi. Perhatikan contoh klasiknya, Fibonacci.

Fibonacci didefinisikan oleh perulangan . Jika Anda menyelesaikan menggunakan rekursi ini, Anda akhirnya melakukan panggilan O ( 2 n ) ke F i b ( Fib(n)=Fib(n1)+Fib(n2)O(2n) , karena pohon rekursi adalah pohon biner dengan tinggi n .Fib()n

Sebagai gantinya, Anda ingin menghitung , lalu gunakan ini untuk menemukan F i b ( 3 ) , gunakan itu untuk menemukan F i b ( 4 ) , dll. Ini hanya membutuhkan waktu O ( n ) .Fib(2)Fib(3)Fib(4)O(n)

DP juga memberi kita teknik dasar untuk menerjemahkan hubungan perulangan menjadi solusi bottom-up, tetapi ini relatif mudah (dan umumnya melibatkan penggunaan matriks dimensi , atau batas matriks seperti itu, di mana m adalah jumlah parameter dalam hubungan perulangan). Ini dijelaskan dengan baik dalam teks tentang DP.mm


1
Anda hanya berbicara tentang bagian memoisasi, yang meleset dari inti pertanyaan.
Raphael

1
"Pemrograman Dinamis memungkinkan Anda untuk bertukar memori dengan waktu komputasi" bukanlah sesuatu yang saya dengar saat melakukan sarjana, dan ini cara yang bagus untuk melihat subjek ini. Ini adalah jawaban intuitif dengan contoh singkat.
trueshot

@trueshot: Kecuali bahwa terkadang pemrograman dinamis (dan khususnya, "Pemrograman Dinamik Pruned") dapat mengurangi kebutuhan waktu dan ruang.
Ben Voigt

@Ben saya tidak mengatakan itu adalah perdagangan satu-ke-satu. Anda dapat memangkas pohon perulangan juga. Saya berpendapat bahwa saya memang menjawab pertanyaan itu, yaitu, "Apa yang DP dapatkan dari kita?" Ini membuat kami lebih cepat menggunakan algoritma berdasarkan ruang perdagangan untuk waktu. Saya setuju bahwa jawaban yang diterima lebih menyeluruh, tetapi ini juga berlaku.
Kittsil

2

Berikut ini adalah cara lain yang sedikit berbeda dari ungkapan apa yang diberikan pemrograman dinamis kepada Anda. Pemrograman dinamis runtuh sejumlah eksponensial solusi kandidat menjadi sejumlah polinomial kelas ekivalensi, sehingga solusi kandidat di setiap kelas tidak bisa dibedakan dalam beberapa hal.

kAn2nO(n2)f(i,)i

f(i,)=j<i such thatA[j]<A[i]f(j,1)
f(i,1)=1 for all i=1n

O(n2k)

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.