動態規劃有時會被認爲是遞歸相反的技術。遞歸是從頂部將問題分解,經過解決分解後的小問題來解決整個問題;動態規劃時從底部解決問題,將全部小問題解決,合併爲總體解決方案。javascript
//經典的斐波那契數列:0,1,1,2,3,5,8,13
//簡單的遞歸實現
function recurFib(n){
if(n<2){ return n }
return recurFib(n-1)+recurFib(n-2)
}
//動態規劃的實現
function dynFib(n){
let arr = [];//記錄小問題的解
//記錄初始值
arr.push(0);arr.push(1);
for(let i=2;i<=n;i++){
arr.push(arr[i-1]+arr[i-2])
}
return arr[n]
}
//能夠不使用數組,直接使用兩個變量記錄前兩個的值
複製代碼
在動態規劃算法中,狀態轉移是關鍵,從上一個狀態到下一個狀態之間可能存在一些變化,基於這些變化獲得最終決策結果。當問題可能不少,可是最終求的是最優解,就能夠試着用動態規劃。java
//最長公共子序列
function lCS(word1,word2){
//創建二維數組,做爲狀態轉移方程
let len1 = word1.length,len2 = word2.length;
let dp = [...new Array(len1+1)].map(() => new Array(len2+1).fill(0));
//let dp = Array.from(new Array(len1 + 1), () => new Array(len2 + 1).fill(0));
//分析狀態轉移方程
for(let i=1;i<=len1;i++){
for(let j=1;j<=len2;j++){
if(word1[i-1] == word2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])
}
}
}
return dp[len1][len2]
}
//若是輸出子串,改變下轉移方程的值便可
let dp = [...new Array(len1+1)].map(() => new Array(len2+1).fill(''));
...
if(word1[i-1] == word2[j-1]){
dp[i][j] = dp[i-1][j-1] + word1[i-1];
}else{
dp[i][j] = dp[i-1][j].length > dp[i][j-1].length?dp[i-1][j]:dp[i][j-1];
}
複製代碼
給定n個重量爲w1,w2,...wn,價值爲v1,v2,...vn的物品,以及容量爲C的揹包,使在知足揹包容量的前提下,包內的總價值最大。算法
遞歸方法解決數組
//c:容量,n:數量,value:價值列表,size:大小列表,size爲有序數組,從小到大
function knapSack(c,n,value,size){
if(c == 0 || n == 0) return 0
if(size[n-1] > c){//去除放不進去的
return knapSack(c,n-1,value,size)
}else{
//當前放進去和不放進去,總價值取最大
return Math.max(value[n-1]+knapSack(c-size[n-1],n-1,value,size),knapSack(c,n-1,value,size))
}
}
//會涉及到反覆取同一個子問題的解
複製代碼
動態規劃:ui
/*找到狀態轉移時變化的量,一個是空間c,一個是數量n 狀態轉移方程:F(i,c) = max(F(i-1,c),F(i-1,c-w[i])+v[i]) */
function knapSack(c,n,value,size){
let dp = [...new Array(n+1)].map(() => new Array(c+1).fill(0));
for(let i=1;i<=n;i++){
for(let j=1;j<=c;j++){
dp[i][j] = dp[i-1][j];
if(size[i-1]<=w){
dp[i][j] = Math.max(value[i-1]+dp[i-1][j-size[i-1]],dp[i-1][j])
}
}
}
return dp[n][c]
}
//簡化爲一維數組,由於當前行的值只與前一行的值有關,爲了防止覆蓋前面的值須要從後往前填充數組
let dp = new Array(c+1).fill(0);
for(i=1;i<=n;i++){
for(let j=c;j>=size[i-1];j--){
dp[j] = Math.max(value[i-1]+dp[j-size[i-1]],dp[j-1])
}
}
return dp[c]
複製代碼
貪心算法老是會選擇當下最優解,經過一系列最優選擇帶來總體的最優選擇。spa
假設貨幣面額有1,2,5,10,20,50,100,每種數量都無限多,如今給出金額n(1<=n<=100000),求出最少的貨幣數量。code
//首先嚐試最大面額找零,以後嘗試次大面額找零,直到徹底找零
function makeChange(n){
let coins = [];
if(n%100 < n){//n比100大
coins.push(parseInt(n/100));
n %= 100
}
...
if(n%1 < n){
coins.push(n/1)
}
return coins
}
複製代碼
1.3的揹包問題是0-1問題,揹包物品是離散的,只能整個放入或者不放入。若是揹包物品是連續的,那就可使用貪心算法,先用價值高的物品填充,接着是次高的...貪心算法能夠解決一部分揹包問題。遞歸
//weights:數組順序按照價值比率從高到底
function ksack(values,weights,c){
let load = 0;
let i=0,w=0;
while(load < c && i<values.length){
if(weights[i] <= (c-load)){
w += values[i];
load += weights[i]
}else{
let r = (c-load)/weights[i];
w += r*values[i];
load += weights[i]
}
i++
}
return w
}
複製代碼