
- 資料結構與演算法
- DSA - 首頁
- DSA - 概述
- DSA - 環境搭建
- DSA - 演算法基礎
- DSA - 漸進分析
- 資料結構
- DSA - 資料結構基礎
- DSA - 資料結構和型別
- DSA - 陣列資料結構
- 連結串列
- DSA - 連結串列資料結構
- DSA - 雙向連結串列資料結構
- DSA - 迴圈連結串列資料結構
- 棧與佇列
- DSA - 棧資料結構
- DSA - 表示式解析
- DSA - 佇列資料結構
- 搜尋演算法
- DSA - 搜尋演算法
- DSA - 線性搜尋演算法
- DSA - 二分搜尋演算法
- DSA - 插值搜尋
- DSA - 跳躍搜尋演算法
- DSA - 指數搜尋
- DSA - 斐波那契搜尋
- DSA - 子列表搜尋
- DSA - 雜湊表
- 排序演算法
- DSA - 排序演算法
- DSA - 氣泡排序演算法
- DSA - 插入排序演算法
- DSA - 選擇排序演算法
- DSA - 歸併排序演算法
- DSA - 希爾排序演算法
- DSA - 堆排序
- DSA - 桶排序演算法
- DSA - 計數排序演算法
- DSA - 基數排序演算法
- DSA - 快速排序演算法
- 圖資料結構
- DSA - 圖資料結構
- DSA - 深度優先遍歷
- DSA - 廣度優先遍歷
- DSA - 生成樹
- 樹資料結構
- DSA - 樹資料結構
- DSA - 樹的遍歷
- DSA - 二叉搜尋樹
- DSA - AVL 樹
- DSA - 紅黑樹
- DSA - B 樹
- DSA - B+ 樹
- DSA - 伸展樹
- DSA - 字典樹
- DSA - 堆資料結構
- 遞迴
- DSA - 遞迴演算法
- DSA - 使用遞迴實現漢諾塔問題
- DSA - 使用遞迴實現斐波那契數列
- 分治法
- DSA - 分治法
- DSA - 最大最小問題
- DSA - Strassen 矩陣乘法
- DSA - Karatsuba 演算法
- 貪心演算法
- DSA - 貪心演算法
- DSA - 旅行商問題(貪心演算法)
- DSA - Prim 最小生成樹
- DSA - Kruskal 最小生成樹
- DSA - Dijkstra 最短路徑演算法
- DSA - 地圖著色演算法
- DSA - 分數揹包問題
- DSA - 帶截止日期的作業排序
- DSA - 最優合併模式演算法
- 動態規劃
- DSA - 動態規劃
- DSA - 矩陣鏈乘法
- DSA - Floyd-Warshall 演算法
- DSA - 0-1 揹包問題
- DSA - 最長公共子序列演算法
- DSA - 旅行商問題(動態規劃)
- 近似演算法
- DSA - 近似演算法
- DSA - 頂點覆蓋演算法
- DSA - 集合覆蓋問題
- DSA - 旅行商問題(近似演算法)
- 隨機化演算法
- DSA - 隨機化演算法
- DSA - 隨機化快速排序演算法
- DSA - Karger 最小割演算法
- DSA - Fisher-Yates 洗牌演算法
- DSA 有用資源
- DSA - 問答
- DSA - 快速指南
- DSA - 有用資源
- DSA - 討論
0-1 揹包問題
在本教程的前面,我們討論了使用貪心演算法解決分數揹包問題。結果表明,貪心演算法可以為分數揹包問題提供最優解。但是,本章將介紹使用動態規劃方法解決 0-1 揹包問題及其分析。
與分數揹包不同,物品總是完整地儲存,而不使用其小數部分。要麼將物品新增到揹包中,要麼不新增。因此,此方法被稱為0-1 揹包問題。
因此,在 0-1 揹包問題中,xi 的值可以是0 或1,其他約束條件保持不變。
0-1 揹包問題不能用貪心演算法解決。貪心演算法不能保證此方法中的最優解。在許多情況下,貪心演算法可能會給出最優解。
0-1 揹包演算法
問題陳述 - 小偷正在搶劫一家商店,他最多可以將W 重量的物品放入他的揹包中。有n 件物品,第 i 件物品的重量為 wi,選擇此物品的利潤為pi。小偷應該拿哪些物品?
令 i 為最優解S(價值為W 元)中編號最高的物品。那麼S’ = S − {i} 是W – wi 元的最優解,而解 S 的值為 Vi 加上子問題的解。
我們可以用以下公式表達這一事實:定義c[i, w] 為物品1,2, … , i 和最大重量w 的解。
該演算法接收以下輸入
最大重量W
物品數量n
兩個序列v = <v1, v2, …, vn> 和 w = <w1, w2, …, wn>
可以從表中推匯出要攜帶的物品集,從c[n, w] 開始,並向後追溯最優值來自哪裡。
如果c[i, w] = c[i-1, w],則物品 i 不屬於解的一部分,我們繼續使用c[i-1, w] 進行追溯。否則,物品 i 是解的一部分,我們繼續使用c [i-1, w-W] 進行追溯。
Dynamic-0-1-knapsack (v, w, n, W) for w = 0 to W do c[0, w] = 0 for i = 1 to n do c[i, 0] = 0 for w = 1 to W do if wi ≤ w then if vi + c[i-1, w-wi] then c[i, w] = vi + c[i-1, w-wi] else c[i, w] = c[i-1, w] else c[i, w] = c[i-1, w]
以下示例將證明我們的陳述。
示例
讓我們考慮一下揹包的容量為 W = 8,物品如以下表格所示。
物品 | A | B | C | D |
---|---|---|---|---|
利潤 | 2 | 4 | 7 | 10 |
重量 | 1 | 3 | 5 | 7 |
解決方案
使用 0-1 揹包的貪心演算法,儲存在揹包中的重量將是 A+B = 4,最大利潤為 2 + 4 = 6。但是,該解決方案將不是最優解決方案。
因此,必須採用動態規劃來解決 0-1 揹包問題。
步驟 1
構造一個鄰接表,揹包的最大重量作為行,物品及其相應的重量和利潤作為列。
表中儲存的值是物品的累積利潤,這些物品的重量不超過揹包的最大重量(每行的指定值)
因此,我們在第 0 行和第 0 列中新增零,因為如果物品的重量為 0,則它不重;如果揹包的最大重量為 0,則無法將任何物品新增到揹包中。

其餘的值將填充與每列中可以儲存在揹包中的物品和重量相關的最大可實現利潤。
儲存利潤值的公式為 -
$$c\left [ i,w \right ]=max\left\{c\left [ i-1,w-w\left [ i \right ] \right ]+P\left [ i \right ] \right\}$$
透過使用該公式計算所有值,獲得的表格將為 -

要查詢要新增到揹包中的物品,請從表中識別最大利潤並識別構成該利潤的物品,在本例中,為 {1, 7}。

最優解是 {1, 7},最大利潤為 12。
分析
該演算法需要 Ɵ(n.w) 時間,因為表 c 有 (n+1).(w+1) 個條目,其中每個條目需要 Ɵ(1) 時間來計算。
實現
以下是使用動態規劃方法實現 0-1 揹包演算法的最終程式碼。
#include <stdio.h> #include <string.h> int findMax(int n1, int n2){ if(n1>n2) { return n1; } else { return n2; } } int knapsack(int W, int wt[], int val[], int n){ int K[n+1][W+1]; for(int i = 0; i<=n; i++) { for(int w = 0; w<=W; w++) { if(i == 0 || w == 0) { K[i][w] = 0; } else if(wt[i-1] <= w) { K[i][w] = findMax(val[i-1] + K[i-1][w-wt[i-1]], K[i-1][w]); } else { K[i][w] = K[i-1][w]; } } } return K[n][W]; } int main(){ int val[5] = {70, 20, 50}; int wt[5] = {11, 12, 13}; int W = 30; int len = sizeof val / sizeof val[0]; printf("Maximum Profit achieved with this knapsack: %d", knapsack(W, wt, val, len)); }
輸出
Maximum Profit achieved with this knapsack: 120
#include <bits/stdc++.h> using namespace std; int max(int a, int b){ return (a > b) ? a : b; } int knapSack(int W, int wt[], int val[], int n){ int i, w; vector<vector<int>> K(n + 1, vector<int>(W + 1)); for(i = 0; i <= n; i++) { for(w = 0; w <= W; w++) { if (i == 0 || w == 0) K[i][w] = 0; else if (wt[i - 1] <= w) K[i][w] = max(val[i - 1] + K[i - 1][w - wt[i - 1]], K[i - 1][w]); else K[i][w] = K[i - 1][w]; } } return K[n][W]; } int main(){ int val[] = { 70, 20, 50 }; int wt[] = { 11, 12, 13 }; int W = 30; int n = sizeof(val) / sizeof(val[0]); cout << "Maximum Profit achieved with this knapsack: " << knapSack(W, wt, val, n); return 0; }
輸出
Maximum Profit achieved with this knapsack: 120
import java.util.*; import java.lang.*; public class Knapsack { public static int findMax(int n1, int n2) { if(n1>n2) { return n1; } else { return n2; } } public static int knapsack(int W, int wt[], int val[], int n) { int K[][] = new int[n+1][W+1]; for(int i = 0; i<=n; i++) { for(int w = 0; w<=W; w++) { if(i == 0 || w == 0) { K[i][w] = 0; } else if(wt[i-1] <= w) { K[i][w] = findMax(val[i-1] + K[i-1][w-wt[i-1]], K[i-1][w]); } else { K[i][w] = K[i-1][w]; } } } return K[n][W]; } public static void main(String[] args) { int[] val = {70, 20, 50}; int[] wt = {11, 12, 13}; int W = 30; int len = val.length; System.out.print("Maximum Profit achieved with this knapsack: " + knapsack(W, wt, val, len)); } }
輸出
Maximum Profit achieved with this knapsack: 120
def knapsack(W, wt, val, n): K = [[0] * (W+1) for i in range (n+1)] for i in range(n+1): for w in range(W+1): if(i == 0 or w == 0): K[i][w] = 0 elif(wt[i-1] <= w): K[i][w] = max(val[i-1] + K[i-1][w-wt[i-1]], K[i-1][w]) else: K[i][w] = K[i-1][w] return K[n][W] val = [70, 20, 50]; wt = [11, 12, 13]; W = 30; ln = len(val); profit = knapsack(W, wt, val, ln) print("Maximum Profit achieved with this knapsack: ") print(profit)
輸出
Maximum Profit achieved with this knapsack: 120