算法學習筆記(8.3)-(0-1背包問題)

目錄

最常見的0-1背包問題:

第一步:思考每輪的決策,定義狀態,從而得到dp表

第二步:找出最優子結構,進而推導出狀態轉移方程

第三步:確定邊界條件和狀態轉移順序

方法一:暴力搜素

代碼示例:

方法二:記憶化搜索

時間復雜度取決于子問題數量,也就是O(n*cap)。

實現代碼如下:

方法三:動態規劃

代碼如下所示:

方法四:空間優化

代碼示例

最常見的0-1背包問題:

Question:給定n個物品,第i個物品的重量為wgt[i]、價值為val[i],和一個容量為cap的背包。每個物品只能選擇一次,問在限定背包的容量下能放入物品的最大價值。

觀察圖下所示,由于物品編號i從1開始計數,數組索引從0開始計數,因此物品i對應重量wgt[i]和價值val[i]。

我們可以將0-1背包問題看作一個由n輪決策組成的過程,對于每個物體都有不放入和放入兩種決策,因此該問題滿足決策樹模型。

該問題的目標是求解“在限定背包容量下能放入物品的最大價值”,因此較大概率是一個動態規劃的問題。

第一步:思考每輪的決策,定義狀態,從而得到dp表

對于每個物品來說,不放入背包,背包容量不變;放入背包,背包容量減小。由此可得狀態定義:當前物品編號i和剩余背包容量c,記作[i,c]。

狀態[i,c]對應的子問題為:前i個物品在剩余容量為c的背包中的最大價值,記為dp[i,c]。

待求解的是dp[n,cap],因此需要一個尺寸為(n+1) * (cap+1)的二維dp表。

第二步:找出最優子結構,進而推導出狀態轉移方程

當我們做出物品i的決策后,剩余的是前i-1個物品的決策,可分為以下兩種情況。

  1. 不放入物品i:背包容量不變,狀態變化為[i-1,c].
  2. 放入物品i:背包容量減少wgt[i-1],價值增加val[i-1],狀態變化為[i-1,c-wgt[i-1]]。

上述分析揭示了本題的最優子結構:最大價值dp[I,c]等于不放入物品i和放入物品i兩種方案中價值更大的那一個。由此可以推導出狀態轉移方程為:

dp[i,c] = max(dp[i-1,c],dp[i-1,c-wgt[i-1] ] + val[i-1])

需要注意是,當前物品重量wgt[i-1]超過剩余背包容量c,則只能選擇不放入背包。

第三步:確定邊界條件和狀態轉移順序

當無物品時或無背包剩余容量時最大價值為0,即首列dp[i,0]和首列dp[0,c]都等于0.

當前狀態[i,c]從上方的狀態[i-1,c]和左上方的狀態[i-1,c-wgt[i-1]]轉移而來,因此通過兩層循環正序遍歷整個dp表即可。

根據以上的分析,采取三種方法順序進行實現暴力搜索,記憶化搜索,動態規劃解法。

方法一:暴力搜素

搜索代碼需要包含的要素:

  1. 遞歸參數:狀態[i,c]
  2. 返回值:子問題的解dp[i,c]
  3. 終止條件:當物品編號越界I = 0 或背包容量為0時,終止遞歸并返回價值0.
  4. 剪枝:當前物品重量超出背包剩余容量時,則只能選擇不放入背包。
代碼示例:
# python 代碼示例def knapsack_dfs(wgt, val, i, c) :if i == 0 or c == 0 :return 0if wgt[i - 1] > c :return knapsack(wgt, val, i - 1, c)nohave = knapsack_dfs(wgt, val, i - 1, c)have = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]return max(nohave, have)
// C++代碼示例int knapsackDFS(vector<int> &wgt, vector<int> &val, int i, int c) :if (i == 0 || c == 0){return 0 ;}if (wgt[i - 1] > c){return knapsackDFS(wgt, val, i - 1, c) ;}int nohave = knapsackDFS(wgt, val, i - 1, c) ;int have = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] ;return max(nohave, have) ;

如圖所示:由于每個物品都會產生選或者不選兩條搜索分支,因此時間復雜度為O(2^n)。

觀察遞歸樹,容易發現其中存在重疊子問題,例如dp[1,10]。而當物品較多,背包容量較大,尤其是相同重量的物品較多時,重疊子問題的數量將會大幅度增多。

方法二:記憶化搜索

為了保證重疊子問題只被計算一次,我們借助記憶列表mem來記錄子問題的解,其中menm[i][c]對應dp[i][c]。

時間復雜度取決于子問題數量,也就是O(n*cap)。
實現代碼如下:
# python 代碼示例
def knapsack_dfs_mem(wgt, val, i, c, mem) :if i == 0 or c == 0 :return 0if mem[i][c] != -1 :return mem[i][c]if wgt[i - 1] > c :return knapsack_dfs_mem(wgt, val, i - 1, c, mem)noput = knapsack_dfs_mem(wgt, val, i - 1, c, mem)yesput = knapsack_dfs_mem(wgt, val, i - 1, c - wgt[i - 1], mem) + val[i - 1]mem[i][c] = max(noput, yesput)return mem[i][c]
// c++ 代碼示例
int knapSackDFSMem(vector<int> &wgt, vector<int> &val, int i, int c, vector<int> &mem)
{if (i == 0 || c == 0){return 0 ;}if (mem[i][c] != 0){return mem[i][c] ;}if (wgt[i - 1] > c){return knapSackDFSMem(wgt, val, i - 1, c, mem) ;}int noput = knapSackDFSMem(wgt, val, i - 1, c, mem) ;int yesput = knapSackDFSMem(wgt, val, i - 1, c - wgt[i - 1], mem) + val[i - 1];mem[i][c] = max(noput, yesput) ;return mem[i][c] ;
}

方法三:動態規劃

動態規劃實質是就是在狀態轉移的過程中填充dp表的過程,

代碼如下所示:
# python 代碼示例
def knapsack_dp(wgt, val, cap) :n = len(wgt)dp = [ [0] * (cap + 1) for _ in range(n + 1)]for i in range(1, n + 1) :for c in range(1, cap + 1) :if wgt[i - 1] > c :dp[i][c] = dp[i - 1][c]else :dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) return dp[n][cap] 
// c++ 代碼示例
int knapSackDP(vector<int> &wgt, vector<int> &val, int cap)
{ int n = wgt.size() ;vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0)) ;for (int i = 1 ; i <= n ; i++){for (int c = 1 ; c <= cap ; c++){if (wgt[i - 1] > c){dp[i][c] = dp[i - 1][c] ;}else{dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])  ;}}}return dp[n][cap] ;
}

時間復雜度和空間復雜度都是由數組dp所決定的,即O(n*cap)。

如圖所示:

方法四:空間優化

由于每個狀態都至于其上一行有關系,因此我們可以使用兩個數組進行滾動前進,將復雜度從O(n^2)降低為O(n)。

進一步思考,我們能否僅用一個數組實現空間優化?觀察可知,每個狀態都是有正上方或左上方的格子的狀態轉移而來。假設只有一個數組,當開始遍歷第i行時,該數組存儲仍然是第i-1行的狀態。

  1. 如果采取正序遍歷,那么遍歷到dp[i,j]時,左上方的dp[i-1,1]~dp[i-1,j-1]值可能已經覆蓋了,因此無法得到狀態轉移的正確結果。
  2. 如果采取倒序遍歷,則不會發生覆蓋問題,狀態轉移可以正確的進行。

代碼示例:
def knap_sack_dp_comp(wgt, val, cap) :n = len(wgt)dp = [0] * (cap + 1)for i in range(1, n + 1) :for c in range(cap, 0 ,-1) :if wgt[i - 1] > c :dp[c] = dp[c]else :dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])return dp[cap]    
// c++ 代碼示例
int kanpSackDPComp(vector<int> &wgt, vector<int> &val, int cap)
{int n = wgt.size() ;vector<int> dp(cap + 1, 0) ;for (int i = 1 ; i <= n ; i++){for (int c = cap; c > 0 ; c--){if (wgt[i - 1] > c){dp[c] = dp[c] ;}else{dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) ;}}}return dp[cap] ;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/45050.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/45050.shtml
英文地址,請注明出處:http://en.pswp.cn/web/45050.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

KBS(Knowledge-Based Systems)期刊投稿記錄

記錄一些關鍵時間節點 2023.12.31 投稿 2024.01.30 返回審稿意見 2024.05.20 提交r1 2024.05.31 返回審稿意見(conditional accept)包括語言潤色 2024.06.09 提交r2&#xff0c;沒有使用愛思維爾的潤色 2024.06.10 with editor 2024.06.13 under review 2024.06.24 revise(折磨…

MFC之對話框--線寬/線型/顏色

文章目錄 線寬輸入實現優化無法記錄上一次線粗問題 線寬滑動實現實現選擇線類型實現顏色選擇總結 線寬輸入實現 優化無法記錄上一次線粗問題 線寬滑動實現 實現選擇線類型 實現顏色選擇 總結 1。創建新窗口&#xff08;dialog)會創建一個新的類&#xff0c;在類中實現窗口中的…

vue中父子傳遞屬性值

1、父傳子屬性值 自定義圖庫組件 在add.vue中應用tuku組件并給默認值 效果 2、 子傳父&#xff0c;逆向賦值 add.vue和第一問中一樣 修改tuku組件&#xff0c;傳值給add.vue 3、多個傳遞 效果&#xff1a; 點擊兩個修改按鈕后 4、使用defineModel簡化父子傳值 其他代碼跟…

【postgresql】時間函數和操作符

日期/時間操作符 加減操作符&#xff1a; 和 - 可以用于日期、時間、時間戳和時間間隔的加減操作。 SELECT 2024-01-01::date INTERVAL 1 day as "date"; ; -- 結果&#xff1a;2024-01-02SELECT 2024-01-01 12:00:00::timestamp - INTERVAL 2 hours as "…

概率論原理精解【2】

文章目錄 笛卡爾積任意笛卡爾積投影映射概述詳解一一、定義二、性質三、應用四、結論 詳解二定義與性質應用與意義示例結論 參考文獻 笛卡爾積 任意笛卡爾積 { A t , t ∈ T } \{A_t,t \in T\} {At?,t∈T}是一個集合族&#xff0c;其中T為一個非空指標集&#xff0c;稱 t ∈…

CSS上下懸浮特效

要實現一個上下懸浮的特效&#xff0c;可以使用CSS的keyframes規則和動畫屬性。以下是一個簡單的示例&#xff1a; 代碼示例 /* 定義一個名為floating的動畫 */ keyframes floating {0% {transform: translateY(0); /* 初始位置 */}50% {transform: translateY(-4px); /* 向上…

M1000 4G藍牙網關:高速穩定,賦能物聯網新體驗

桂花網M1000的4G移動網絡功能主要體現在以下幾個方面&#xff1a; 一、高速穩定的數據傳輸 高速率&#xff1a;M1000支持4G移動網絡&#xff0c;能夠實現高速的數據傳輸。根據4G網絡的技術標準&#xff0c;其理論上的最大下行速率可達到數百Mbps&#xff08;如TD-LTE在20MHz帶…

KALI使用MSF攻擊安卓設備

這期是kali使用MSF進行安卓滲透的保姆級別教程&#xff0c;話不多說&#xff0c;直接開始。 準備材料&#xff1a; 1.裝有kali的實體機或虛擬機&#xff08;這里用實體機進行演示&#xff09; 2.一臺安卓10.0以下的手機 打開kali&#xff0c;先用ifconfig查看自己的kali IP地址…

Python3極簡教程(一小時學完)下

目錄 PEP8 代碼風格指南 知識點 介紹 愚蠢的一致性就像沒腦子的妖怪 代碼排版 縮進 制表符還是空格 每行最大長度 空行 源文件編碼 導入包 字符串引號 表達式和語句中的空格 不能忍受的情況 其他建議 注釋 塊注釋 行內注釋 文檔字符串 版本注記 命名約定 …

[BJDCTF2020]EasySearch1

知識點&#xff1a; 1.swp泄露 2.md5碰撞 3.PHP代碼審計 4.SSI代碼執行漏洞 // Apache SSI 遠程命令執行漏洞復現 看著像sql注入&#xff0c;不過注入無果&#xff0c;掃一下目錄試試~ 發現是swp泄露. SWP文件泄露漏洞是指在使用 Vim編輯器 編輯一個文件時&#xff0c;Vim會在…

codeforce 954 div3 G2題

思路&#xff1a; 質因子分解可以順著分解&#xff0c;也可以逆著分解 即找到每一個數字的倍數&#xff0c;再找到每一個數字的因數 const int N 5e510; vector<int> ff[N]; vector<int> f[N]; vector<int> g[N];void solve(){int n;cin>>n;vector&l…

OpenCV漫水填充函數floodFill函數的使用

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 功能描述 ffloodFill函數是OpenCV庫中用于圖像處理的一個功能&#xff0c;它用于填充與種子點顏色相近的連通區域。這個函數在很多場景下都非常有用&#x…

MUR2060CTR-ASEMI無人機專用MUR2060CTR

編輯&#xff1a;ll MUR2060CTR-ASEMI無人機專用MUR2060CTR 型號&#xff1a;MUR2060CTR 品牌&#xff1a;ASEMI 封裝&#xff1a;TO-220 批號&#xff1a;最新 最大平均正向電流&#xff08;IF&#xff09;&#xff1a;20A 最大循環峰值反向電壓&#xff08;VRRM&#…

【數據結構】線性表----隊列詳解

1. 隊列的基本概念 話不多說&#xff0c;直接開始&#xff01; 隊列是一種線性數據結構&#xff0c;同棧類似但又不同&#xff0c;遵循先進先出&#xff08;FIFO, First In First Out&#xff09;的原則。換句話說&#xff0c;最先進入隊列的元素會最先被移除。這樣的特點使得…

小白學python(第七天)

哈哈&#xff0c;這個系列的文章也有一段時間沒更新&#xff0c;主要是最近在忙c嘎嘎&#xff0c;不過沒事接下來會優先更python啦&#xff0c;那么我們先進入正題吧 函數的定義及調用 函數定義 格式&#xff1a;def 函數名&#xff08;形參列表&#xff09;&#xff1a; 語…

QTabWidget、QListWidget、QStackedWidget

The QTabWidget class provides a stack of tabbed widgets. More... The QListWidget class provides an item-based list widget. More... QStringList strlist;strlist<<"系統"<<"外觀"<<"截圖"<<"貼圖"…

.NET MAUI開源架構_4..NET MAUI 應用支持的平臺

可以針對以下平臺編寫 .NET Multi-platform App UI (.NET MAUI) 應用&#xff1a; 需要 Android 5.0 (API 21) 或更高版本。需要 iOS 11 或更高版本使用 Mac Catalyst 的 macOS 11 或更高版本。Windows 11 和 Windows 10 版本 1809 或更高版本&#xff0c;使用 Windows UI 庫 …

Java的高級特性

類的繼承 繼承是從已有的類中派生出新的類&#xff0c;新的類能擁有已有類的屬性和行為&#xff0c;并且可以拓展新的屬性和行為 public class 子類 extends 父類{子類類體 } 優點 代碼的復用 提高編碼效率 易于維護 使類與類產生關聯&#xff0c;是多態的前提 缺點 類缺乏獨…

c/c++ 打印調用棧

打印調用棧可以在程序出現死機的時候&#xff08;如出現 SIGABRT、SIGSEGV等一些信號錯誤&#xff09;是很有用的信息&#xff0c;有可能就不需要 core file 來協助排查問題了。通過 man backtrace 可以得到一個例子的源碼&#xff1a; #define SIZE 100 static void backTrac…

【機器學習-00】機器學習是什么?

在科技飛速發展的今天&#xff0c;機器學習已成為一個熱門話題&#xff0c;廣泛應用于各個行業和領域。那么&#xff0c;機器學習到底是什么&#xff1f;它又是如何工作的&#xff1f;本文將深入探討機器學習的定義、原理及其在各領域的應用&#xff0c;帶領讀者走進這個神秘而…