Deret Fibonacci
Menggunakan pemrograman dinamis dalam perhitungan anggota ke-n deret Fibonacci meningkatkan kinerjanya secara signifikan. Berikut adalah implementasi naif, berdasarkan langsung pada definisi matematis:
function fib(n)
if n <= 1 return n
return fib(n − 1) + fib(n − 2)
Perhatikan bahwa jika kita sebut, katakanlah, fib(5), kita menghasilkan pohon panggilan yang memanggil fungsi pada nilai yang sama berkali-kali:
fib(5)
fib(4) + fib(3)
(fib(3) + fib(2)) + (fib(2) + fib(1))
((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
(((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
Khususnya, fib(2) dihitung tiga kali dari awal. Dalam contoh yang lebih besar, lebih banyak nilai fib, atau subproblem, dihitung ulang, yang mengarah ke algoritma waktu eksponensial.
Sekarang, misalkan kita memiliki objek peta sederhana, m, yang memetakan setiap nilai fib yang telah dihitung ke hasilnya, dan kita memodifikasi fungsi kita untuk menggunakannya dan memperbaruinya. Fungsi yang dihasilkan hanya membutuhkan O(n) waktu, bukan waktu eksponensial (tetapi membutuhkan O(n) ruang):
var m := map(0 → 0, 1 → 1)
function fib(n)
if key n is not in map m
m[n] := fib(n − 1) + fib(n − 2)
return m[n]
Teknik menyimpan nilai yang telah dihitung ini disebut memoization; ini adalah pendekatan top-down, karena kita pertama kali memecah masalah menjadi subproblem lalu menghitung dan menyimpan nilai.
Dalam pendekatan bottom-up, kita menghitung nilai fib yang lebih kecil terlebih dahulu, lalu buat nilai yang lebih besar darinya. Metode ini juga menggunakan waktu O(n) karena mengandung loop yang berulang n - 1 kali, tetapi hanya membutuhkan ruang konstan (O(1)), berbeda dengan pendekatan top-down yang membutuhkan ruang O(n) untuk simpan peta.
function fib(n)
if n = 0
return 0
else
var previousFib := 0, currentFib := 1
repeat n − 1 times // loop is skipped if n = 1
var newFib := previousFib + currentFib
previousFib := currentFib
currentFib := newFib
return currentFib
Dalam kedua contoh tersebut, kita hanya menghitung fib(2) satu kali, lalu gunakan untuk menghitung keduanya fib(4) dan fib(3), alih-alih menghitungnya setiap kali salah satu dari mereka dievaluasi.
Metode di atas sebenarnya membutuhkan
waktu untuk n besar karena penjumlahan dua bilangan bulat dengan
bit masing-masing mengambil
waktu. (Nomor nth fibonacci memiliki
bit.) Juga, ada bentuk tertutup untuk deret Fibonacci, yang dikenal sebagai rumus Binet, yang darinya suku
-th dihitung kira-kira
waktu, yang lebih efisien daripada teknik pemrograman dinamis di atas. Namun, pengulangan sederhana secara langsung memberikan bentuk matriks yang mengarah ke perkiraan
algoritma dengan eksponensial matriks cepat.
Perataan urutan
Dalam genetika, perataan urutan adalah aplikasi penting di mana pemrograman dinamis sangat penting.[11] Biasanya, masalahnya terdiri dari mengubah satu urutan menjadi urutan lain menggunakan operasi edit yang mengganti, menyisipkan, atau menghapus elemen. Setiap operasi memiliki biaya terkait, dan tujuannya adalah menemukan urutan pengeditan dengan total biaya terendah.
Masalahnya dapat dinyatakan secara alami sebagai rekursi, urutan A diedit secara optimal menjadi urutan B dengan baik:
- memasukkan karakter pertama B, dan melakukan penyelarasan optimal A dan ekor B
- menghapus karakter pertama A, dan melakukan penyelarasan optimal pada ekor A dan B
- mengganti karakter pertama A dengan karakter pertama B, dan melakukan penjajaran optimal pada ekor A dan B.
Perataan parsial bisa ditabulasi dalam matriks, di mana sel (i,j) berisi biaya penyelarasan yang optimal A[1..i] ke B[1..j]. Biaya dalam sel (i,j) dapat dihitung dengan menambahkan biaya operasi yang relevan dengan biaya sel tetangganya, dan memilih yang optimal.
Ada varian yang berbeda, lihat algoritma Smith – Waterman dan algoritma Needleman – Wunsch.