大前端演算法篇之揹包問題
阿新 • • 發佈:2022-02-13
簡述:揹包問題是動態規劃演算法中的一個經典問題,分為01揹包和完全揹包,01揹包就是不能放入同一件物品,完全揹包是可以放入同一個物品
下面將要講的是01揹包問題
動態規劃中最重要的是先分析思路,然後總結出規律,最後得出一個公式
案例:假設有一個揹包,可以放入單位重量5的物品,然後我們有三個物品
物品編號/屬性 | 物品1 | 物品2 | 物品3 |
單位重量 | 1 | 3 | 4 |
價值 | 2 | 4 | 5 |
如果在不超過揹包最大承受重量的情況下如何放入物品,能夠使得揹包內物品的價值最大化。
對於這種問題,我們可以先填一個表格,我們用r(row)表示行,c(col)表示列,w[r]表示第r個物品的重量,c就是揹包的容量,v[r][c]表示前r個物品可以放入當前揹包重量c的最大價值,singleV(r) 代表第r個物品的價值
揹包容積(單位重量)/物品編號 | 0 | 1 | 2 | 3 | 4 | 5 |
沒有物品 | 0 | 0 | 0 | 0 | 0 | 0 |
物品1 | 0 | 2 | 2 | 2 | 2 | 2 |
物品2 | 0 | 2 | 2 | 4 | 6 | 6 |
物品3 | 0 | 2 | 2 | 4 | 6 | 7 |
對錶格的解讀:
- v[r][0] = 0 ;v[0][c] = 0;表示在揹包容量為0,或者沒有物品時可能放入的最大價值就是0
- 如果當前要放入的物品大於揹包的容量,那就直接取上一次揹包的最大價值,也就是 w[r] > c 那麼 v[r][c] = v[r-1][c]
- 如果當前要放入的物品小於等於揹包的容量,那就取 當前放入的價值加上剩餘空間可以放入的最大價值 和 不放入當前物品時的價值 兩者之間取最大值,也就是 w[r] <= c 那麼 v[r][c] = max(v[r-1][c], singleV(r) + v[r-1][c-w[r])
我們可以隨便拿表格中的最右下角那個值來驗證一下,根據我們上面的推斷:
此時 r = 3, w[r] = 4 , c = 5 符號第3點規則 ,v[3][5] = max(v[3-1][5], singleV(3) + v[3-1][5-4]) = max(6,5 + 2) = 7 整合就是表格中的值
接下來用程式碼寫一下這個演算法
// 揹包問題演算法 function knapsackProblem() { let itemWeights = [1, 3, 4] // 物品的重量 let itemValus = [2, 4, 5] // 物品的價值 let weight = 5 // 揹包的容積 let nums = itemWeights.length // 物品的個數 // 代表放入最大價值的二維陣列 let v = Array.from({ length: nums + 1 }).map(item => Array.from({ length: weight + 1 })) // 列印輸出一下上面的表格 for (let r = 0; r < v.length; r++) { for (let c = 0; c < v[0].length; c++) { v[r][c] = 0 } } // console.log(v) // 這時輸出的全是0 ,因為我們沒有判斷是否可以放入物品 /** * [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0 ] ] */ // 接下來根據我們上面的規則放入物品,因為第一行和第一列全是0,所以我們行和列從1開始 for (let r = 1; r < v.length; r++) { for (let c = 1; c < v[0].length; c++) { if (itemWeights[r - 1] > c) { v[r][c] = v[r - 1][c] } else { v[r][c] = Math.max(v[r - 1][c], itemValus[r - 1] + v[r - 1][c - itemWeights[r - 1]]) } } } console.log(v) /** * 這裡我們就得到了上面的表格,這裡我們可以看出7就是能夠放入的最大價值, * 但此時我們還不知道里面放入了那些物品,所以我們還得記錄放入的物品 * [ [ 0, 0, 0, 0, 0, 0 ], [ 0, 2, 2, 2, 2, 2 ], [ 0, 2, 2, 4, 6, 6 ], [ 0, 2, 2, 4, 6, 7 ] ] */ // 用來記錄 let records = Array.from({ length: nums + 1 }).map(item => Array.from({ length: weight + 1 })) for (let r = 1; r < v.length; r++) { for (let c = 1; c < v[0].length; c++) { if (itemWeights[r - 1] > c) { v[r][c] = v[r - 1][c] } else { // v[r][c] = Math.max(v[r - 1][c], itemValus[r - 1] + v[r - 1][c - itemWeights[r - 1]]) if (v[r - 1][c] < itemValus[r - 1] + v[r - 1][c - itemWeights[r - 1]]) { v[r][c] = itemValus[r - 1] + v[r - 1][c - itemWeights[r - 1]] // 只在此記錄,因為這裡是已經把當前物品放入了 records[r][c] = true } else { v[r][c] = v[r - 1][c] } } } } // for (let i = 0; i < records.length; i++) { // for (let j = 0; j < records[0].length; j++) { // if (records[i][j]) { // console.log(`第${i}個物品放入揹包`) // } // } // } /** * 通過上面的列印我們還是不知道具體是哪些物品放入了揹包, * 我們可以從後往前遍歷 * 第1個物品放入揹包 第1個物品放入揹包 第1個物品放入揹包 第1個物品放入揹包 第1個物品放入揹包 第2個物品放入揹包 第2個物品放入揹包 第2個物品放入揹包 第3個物品放入揹包 */ let i = records.length - 1 let j = records[0].length - 1 while (i > 0) { if (records[i][j]) { console.log(`第${i}個物品放入揹包`) j -= itemWeights[i - 1] } i-- } /** * 這裡我們就知道哪些物品放入揹包了 * 第3個物品放入揹包 第1個物品放入揹包 */ } knapsackProblem()