算法设计与分析第4章 动态规划(一)【背包问题】
第3章動態規劃(一)【背包問題】
基本思想:
動態規劃算法與分治法類似,其基本思想也是將待求解問題分解成若干個子問題,但是經分解得到的子問題往往不是互相獨立的。不同子問題的數目常常只有多項式量級。在用分治法求解時,有些子問題被重復計算了許多次。如果能夠保存已解決的子問題的答案,而在需要時再找出已求得的答案,就可以避免大量重復計算,從而得到多項式時間算法。
基本要素:
1.最優子結構性質
問題的最優解包含著其子問題的最優解。這種性質稱為最優子結構性質。
在分析問題的最優子結構性質時,所用的方法具有普遍性:首先假設由問題的最優解導出的子問題的解不是最優的,然后再設法說明在這個假設下可構造出比原問題最優解更好的解,從而導致矛盾。
利用問題的最優子結構性質,以自底向上的方式遞歸地從子問題的最優解逐步構造出整個問題的最優解。最優子結構是問題能用動態規劃算法求解的前提。
2.重疊子問題性質
遞歸算法求解問題時,每次產生的子問題并不總是新問題,有些子問題被反復計算多次。這種性質稱為子問題的重疊性質。
動態規劃算法,對每一個子問題只解一次,而后將其解保存在一個表格中,當再次需要解此子問題時,只是簡單地用常數時間查看一下結果。
通常不同的子問題個數隨問題的大小呈多項式增長。因此用動態規劃算法只需要多項式時間,從而獲得較高的解題效率。
3.備忘錄方法
備忘錄方法的控制結構與直接遞歸方法的控制結構相同,區別在于備忘錄方法為每個解過的子問題建立了備忘錄以備需要時查看,避免了相同子問題的重復求解。
基本步驟:
1.找出最優解的性質,并刻劃其結構特征。
2.遞歸地定義最優值。
3.以自底向上的方式計算出最優值。
4.根據計算最優值時得到的信息,構造最優解。
3.1 背包問題(問題分析來自HTML Help)
背包問題(Knapsack problem)是一種組合優化的NP完全問題。
1.01背包問題
有N件物品和一個容量為V的背包。第i件物品的重量是wi[i],價值是val[i]。求解將哪些物品裝入背包可使這些物品的重量總和不超過背包容量,且價值總和最大。
最基礎的背包問題,特點是:每種物品僅有一件,可以選擇放或不放。
用子問題定義狀態:即DP[i][v]表示前i件物品恰放入一個容量為v的背包可以獲得的最大價值。則其狀態轉移方程是:
DP[i][v]=max{ DP[i-1][v], DP[i-1][v-wi[i]]+val[i] }。
(可以壓縮空間,DP[v]=max{DP[v],DP[v-wi[i]]+val[i]})
方程解釋:“將前i件物品放入容量為v的背包中"這個子問題,若只考慮第i件物品的策略(放或不放),那么就可以轉化為一個只涉及前i-1件物品的問題。如果不放第i件物品,問題轉化為"前i-1件物品放入容量為v的背包中”,價值為DP[i-1][v];如果放第i件物品,問題轉化為"前i-1件物品放入剩下的容量為v-wi[i]的背包中",此時能獲得的最大價值就是DP [i-1][v-wi[i]]再加上通過放入第i件物品獲得的價值val[i]。
優化空間復雜度
DP[i][v]是由DP[i-1][v]和DP[i-1] [v-wi[i]]兩個子問題遞推而來,能否保證在推DP[i][v]時(也即在第i次主循環中推DP[v]時)能夠得到DP[i-1][v]和DP[i-1] [v-wi[i]]的值呢?事實上,這要求在每次主循環中我們以v=V…0的順序推DP[v],這樣才能保證推DP[v]時DP[v-wi[i]]保存的是狀態 DP[i-1][v-wi[i]]的值。偽代碼如下:
for i=1…N
for v=V…0
DP[v]=max{DP[v],DP[v-wi[i]]+val[i]};
算法復雜度:時間復雜度:O(VN),壓縮空間后空間復雜度O(V)
代碼:
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<cstdio>
#include<cmath>
#include<string>
#include<algorithm>
using namespace std;
int DP[1010],wi[1010],val[1010];
int main()
{int T;scanf("%d",&T);while(T--){int i,j,n,v;//初始化的細節問題 //(1)要求恰好裝滿背包,初始化時:DP[0]=0,DP[1..v]=-∞,這樣就可以保證最終得到的DP[N]是一種恰好裝滿背包的最優解。//(2)沒有要求必須把背包裝滿,只希望價格盡量大,初始化時:DP[0..v]=0。memset(DP,0,sizeof(DP));memset(wi,0,sizeof(wi));memset(val,0,sizeof(val));scanf("%d%d",&n,&v);for(i=1; i<=n; i++){scanf("%d",&val[i]);}for(i=1; i<=n; i++){scanf("%d",&wi[i]);}for(i=1; i<=n; i++){for(j=v; j>=wi[i]; j--){DP[j]=max(DP[j],DP[j-wi[i]]+val[i]);}}for(i=1; i<=v; i++){printf("%d ",DP[i]);}printf("\n");}return 0;
}
2.完全背包
有N種物品和一個容量為V的背包,每種物品都有無限件可用。第i種物品的費用是c[i],價值是w[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。
使用一維數組,偽代碼:
for i=1…N
for v=0…V
DP[v]=max{DP[v],DP[v-cost]+weight}
分析:與01背包的偽代碼只有v的循環次序不同。01背包中要按照v=V…0的逆序來循環是因為要保證第i次循環中的狀態DP[i][v]是由狀態DP[i-1] [v-val[i]]遞推而來。換句話說,這正是為了保證每件物品只選一次,保證在考慮“選入第i件物品”這件策略時,依據的是一個絕無已經選入第i件物品的 子結果DP[i-1][v-wi[i]]。而現在完全背包的特點恰是每種物品可選無限件,所以在考慮“加選一件第i種物品”這種策略時,卻正需要一個可能已選入第i種物品的子結果DP[i][v-wi[i]],所以就可以并且必須采用v=0…V的順序循環。
【hdu1114】代碼:
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<cstdio>
#include<cmath>
#include<string>
#include<algorithm>
using namespace std;
#define INF 0x3fffffff
int p[10010],w[10010],dp[10010];
int main()
{int t;scanf("%d",&t);while(t--){int e,f,v,i,j,n;scanf("%d%d",&e,&f);v=f-e;scanf("%d",&n);for(i=1; i<=n; i++){scanf("%d%d",&p[i],&w[i]);}for(i=1; i<=v; i++){dp[i]=INF;}for(i=1; i<=n; i++){for(j=w[i]; j<=v; j++){dp[j]=min(dp[j],dp[j-w[i]]+p[i]);}}/* for(j=1; j<=v; j++){printf("%d ",dp[j]);}printf("\n");*/if(dp[v]==INF){printf("This is impossible.\n");}else{printf("The minimum amount of money in the piggy-bank is %d.\n",dp[v]);}}return 0;
}
3.多重背包問題
有N種物品和一個容量為V的背包。第i種物品最多有n[i]件可用,每件費用是c[i],價值是w[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。
可轉化為01背包求解:把第i種物品換成n[i]件01背包中的物品,則得到了物品數為Σn[i]的01背包問題,直接求解,復雜度仍然是O(V*Σn[i])。
方法:將第i種物品分成若干件物品,其中每件物品有一個系數,這件物品的費用和價值均是原來的費用和價值乘以這個系數。使這些系數分別為 1,2,4,…,2(k-1),n[i]-2k+1,且k是滿足n[i]-2^k+1>0的最大整數。例如,如果n[i]為13,就將這種 物品分成系數分別為1,2,4,6的四件物品。
算法復雜度:將第i種物品分成了O(log n[i])種物品,將原問題轉化為了復雜度為O(V*Σlog n[i])的01背包問題。
代碼:
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<cstdio>
#include<cmath>
#include<string>
#include<algorithm>
using namespace std;
int dp[200];
void CompletePack(int cost, int weight,int n)
{int i;for(i=cost; i<=n; i++)dp[i]=max(dp[i],dp[i-cost]+weight);
}
void Zero_OnePack(int cost,int weight,int n)
{int i;for(i=n; i>=cost; i--)dp[i]=max(dp[i],dp[i-cost]+weight);
}
int main()
{int cost[200],weight[200],amount[200];int i,j,k;int t,n,m;scanf("%d",&t);while(t--){scanf("%d%d",&n,&m);for(i=0; i<m; i++)scanf("%d%d%d",&cost[i],&weight[i],&amount[i]);memset(dp,0,sizeof(dp));for(i=0; i<m; i++){if(cost[i]*amount[i]>n) CompletePack(cost[i],weight[i],n);else{k=1;while(k<amount[i]){Zero_OnePack(k*cost[i],k*weight[i],n);amount[i]-=k;k*=2;}Zero_OnePack(amount[i]*cost[i],amount[i]*weight[i],n);}}printf("%d\n",dp[n]);}return 0;
}
總結
以上是生活随笔為你收集整理的算法设计与分析第4章 动态规划(一)【背包问题】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为啥腾迅视频没有流浪地球和奔驰人生这次两
- 下一篇: 《OpenCV3编程入门》学习笔记1 邂