JavaScript中的高級算法-動態規劃&貪心算法

1.動態規劃

動態規劃有時會被認爲是遞歸相反的技術。遞歸是從頂部將問題分解,經過解決分解後的小問題來解決整個問題;動態規劃時從底部解決問題,將全部小問題解決,合併爲總體解決方案。javascript

1.1斐波那契數列(LeetCode):
//經典的斐波那契數列: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]
}
//能夠不使用數組,直接使用兩個變量記錄前兩個的值
複製代碼
1.2最長公共子串(LeetCode):

在動態規劃算法中,狀態轉移是關鍵,從上一個狀態到下一個狀態之間可能存在一些變化,基於這些變化獲得最終決策結果。當問題可能不少,可是最終求的是最優解,就能夠試着用動態規劃。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];
}
複製代碼
1.3 揹包問題

給定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]
複製代碼

2.貪心算法

貪心算法老是會選擇當下最優解,經過一系列最優選擇帶來總體的最優選擇。spa

2.1 找零問題

假設貨幣面額有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
}
複製代碼
2.2 揹包問題

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
}
複製代碼
相關文章
相關標籤/搜索