去年由於工做中的某個應用場景,須要使用到動態規劃,爲此花了些時間啃了啃揹包算法前端
爲啥去年的東西,今年才寫粗來,也許大概是懶吧算法
先簡單說下什麼是動態規劃數組
引用維基百科的一句話 經過把原問題分解爲相對簡單的子問題的方式求解複雜問題的方法
,通俗的理解就是,將一個複雜的問題拆解成類似的多個子問題,而後依次解決各個子問題,同時由於子問題的類似性,在計算過程當中,須要同時將子問題的結果保存起來,在下一個一樣的子問題時,直接查找便可,這樣在一樣的問題上就不會再次花費計算時間,其核心思想就是拆解與記錄ui
那麼什麼狀況下可使用動態規劃呢 ? 需具有如下特徵spa
有最優子結構,若是一個問題最優的解決方案能夠經過最優它的子問題的解決方案來獲取,那麼這個問題就有最優子結構的屬性,簡單的理解就是用子問題的最優解來構造原問題的最優解.net
子問題無後效性,即子問題的解一旦算出,就不會受到後續的子問題的影響而發生改變設計
子問題重疊性質,即每次產生的子問題都不是一個全新的問題,是在前一個子問題的基礎上變化而來的,因此子問題之間須要具備重疊性,這也是動態規劃高效率的一個緣由code
總結以下:對象
先看一個場景:給定一組物品,每種物品都有本身的重量和價格,在限定的總重量內,咱們如何選擇,才能使得物品的總價格最高?blog
那麼爲解決這一類問題的算法就稱爲揹包算法。
適用於揹包問題的場景須要具有如下特徵
咱們能夠將揹包的容量(v)拆解,分爲0到v不一樣的揹包,那麼子問題就是,容量爲(0-v)的揹包,咱們要算出對應的各個揹包所能容納下的最大價值,聰明的小夥伴這時候必定想起了上面咱們介紹的動態規劃,沒錯!揹包問題的最優解法就是動態規劃,它徹底符合動態規劃的特徵。
揹包算法分類主要分爲三種,其餘的場景是在這三種的基礎上混合和擴展
目前只瞭解01揹包算法,下面就只針對01揹包算法作介紹
01揹包:有N件物品和一個容量爲V的揹包。每種物品均只有一件,能夠選擇放或不放,第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可以使價值總和最大。
01揹包問題是最基本的揹包問題,它包含了揹包問題中設計狀態、方程的最基本思想,另外,別的類型的揹包問題每每也能夠轉換成01揹包問題求解
算法的核心是狀態轉移方程:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
至於這個方程怎麼得來的,暫時超粗個人能力範圍啦~歡迎懂得小夥伴教教
解釋一下上面的方程:f[i][v]
表示前i件物品恰放入一個容量爲v的揹包能夠得到的最大價值,即算出子問題f[i][v]
的解。
舉個栗子,有編號分別爲a,b,c,d,e的五件物品,它們的重量分別是3,6,3,8,6,它們的價值分別是4,6,6,12,10如今給你個承重爲10的揹包,如何讓揹包裏裝入的物品具備最大的價值
根據狀態轉移方程,咱們先弄個表格,用於觀察子問題的解
name | weight | value | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
a | 3 | 4 | ||||||||||
b | 6 | 6 | ||||||||||
c | 3 | 6 | ||||||||||
d | 8 | 12 | ||||||||||
e | 6 | 10 |
第一步,f[a][1]
,a物品放入容量爲1的揹包中,求其最大價值,爲方便描述就簡稱 a1單元格,很顯然放不下,那麼解爲0
再算 a2單元格,根據方程 f[a][2]=max{f[a-1][2],f[a-1][2-c[a]]+w[a]} = max{0,0}
可得 a2 = 0
再算 a3, 根據方程 f[a][3]=max{f[a-1][3],f[a-1][3-c[a]]+w[a]} = max{0,0+4}
可得 a3 = 4
依次算出 a行的值,接下來到b行的
f[b][1]=max{f[b-1][1],f[b-1][1-c[b]]+w[b]} = max{0,0+}
可得 b1 = 0
f[b][3]=max{f[b-1][3],f[b-1][3-c[b]]+w[b]} = max{4,0}
可得 b1 = 4
f[b][6]=max{f[b-1][6],f[b-1][6-c[b]]+w[b]} =max{f[a][6],f[a][6-6]+6} = max{4,0+6}
可得 b1 = 6
f[b][9]=max{f[b-1][9],f[b-1][9-c[b]]+w[b]} =max{f[a][9],f[a][9-6]+6} = max{4,4+6}
可得 b1 = 10
依次類推,直到把表填滿
name | weight | value | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
a | 3 | 4 | 0 | 0 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 |
b | 6 | 6 | 0 | 0 | 4 | 4 | 4 | 6 | 6 | 6 | 10 | 10 |
c | 3 | 6 | 0 | 0 | 6 | 6 | 6 | 10 | 10 | 10 | 12 | 12 |
d | 8 | 12 | 0 | 0 | 6 | 6 | 6 | 10 | 10 | 12 | 12 | 12 |
e | 6 | 10 | 0 | 0 | 6 | 6 | 6 | 10 | 10 | 12 | 16 | 16 |
那麼能夠知道容量爲10的揹包,最大的價值爲16,那麼對應的是那些物品呢?可經過回溯法,找回對應的商品,從表格右下角開始找起
最優紅包組合:用戶在下單結算的時候,在用戶的n多個紅包中,爲用戶算出最優的紅包組合,合併給用戶一塊兒使用,紅包有兩個要素:紅包金額、紅包最低使用金額,且組合的紅包中,紅包最低使用金額的總和不能大於訂單金額,那麼哪幾個紅包組合纔是最大的優惠呢?該問題簡單一句話歸納就是:
根據下單金額 W 從 N 個紅包中選出一個或多個紅包,算出當前下單金額下最優的紅包組合
這是一個揹包問題,知足01揹包的特徵,將下單金額當作是揹包容量,每一個紅包是一個物品,物品的價值是紅包的金額,物品的重量是紅包的最低適用金額,子問題就是,求金額(0-w)下適用的最優紅包組合
紅包對象
{
"amount": "39.00", // 紅包金額-商品的價值
"rangeBegin": "6000.00", // 紅包最低適用金額-商品的重量
}
複製代碼
具體代碼實現以下
/** * @description * 01揹包算法 * @private * @param {any} dataList 紅包列表 * @param {any} all 下單金額 * @returns */
private knapsack(dataList, all) {
const returnList = [];
for (let i = 0; i < dataList.length; i++) {
// 構建二維數組
returnList[i] = [];
for (let j = 0; j < all; j++) { // 分割揹包
const currentBagWeight = j + 1; // 此時揹包重量
const currentWeight = dataList[i].rangeBegin; // 此時物品重量
const currentValue = dataList[i].amount; // 此時的價值
const lastW = currentBagWeight - currentWeight; // 此時揹包重量減去此時要添加的物品後的重量
// 求子問題最優解,並記錄
let fV = lastW >= 0 ? currentValue : 0;
fV = fV + (i > 0 && returnList[i - 1][lastW - 1] ? returnList[i - 1][lastW - 1] : 0);
const nV = i > 0 && returnList[i - 1][j] ? returnList[i - 1][j] : 0;
returnList[i][j] = Math.max(fV, nV);
}
}
// 回溯算法,算出選擇的商品
let y = all - 1;
const selectItem = [];
let i = dataList.length - 1;
while (i > -1) {
if (returnList[i][y] === (returnList[i - 1] && returnList[i - 1][y - dataList[i].rangeBegin] || 0) + dataList[i].amount) {
selectItem.push(dataList[i]);
y -= dataList[i].rangeBegin;
}
i--;
}
return selectItem;
}
複製代碼
誰說前端狗就不用懂算法的~ 哈哈~ 不要給本身設限~
參考文章 揹包問題九講 動態規劃之01揹包問題