1008-----算法笔记----------0-1背包问题(动态规划求解)
1.問題描述
給定n種物品和一個(gè)背包,物品i的重量是wi,其價(jià)值為vi,背包的容量為C。問:應(yīng)該如何選擇裝入背包的物品,使得裝入背包中物品的總價(jià)值最大?
2.問題分析
上述問題可以抽象為一個(gè)整數(shù)規(guī)劃問題,即求滿足 (a)Σwixi?≤ C;(b)xi ∈(0,1), ?1≤i≤n;條件下,∑vixi最大時(shí)的一個(gè)物品xi序列。分析問題可以發(fā)現(xiàn),該問題具有最優(yōu)子結(jié)構(gòu)性質(zhì),那么就可以嘗試用動(dòng)態(tài)規(guī)劃方法求解,而動(dòng)態(tài)規(guī)劃求解的關(guān)鍵就是列出問題的遞歸關(guān)系表達(dá)式。
設(shè)m(i,j)為背包容量為j,可選物品為i,i+1,...n時(shí)0-1背包問題的最優(yōu)質(zhì),那么可有如下遞歸式:
m(i,j) =?{?max( m(i+1, j), m(i+1, j-wi)+vi);? j>=wi;
? ? ??{ m(i+1, j); j<wi;
要求的是m(1,c),此時(shí)問題就轉(zhuǎn)化為填充m數(shù)組的問題了,以n = 5, c = 10, w[] = {2,2,6,5,4},v[] = {6,3,5,4,6},填充的過程如下圖所所示,主要是用上述遞歸式求值,考慮當(dāng)前物品能否放入,放入當(dāng)前物品和不放入導(dǎo)致最終的價(jià)值哪個(gè)大,圖中陰影部分為回溯求xi的過程,表示0,1,4號(hào)物品被放入背包中。
| i/j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 4 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
| 3 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
| 2 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 15 |
| 1 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 9 | 9 |
| 0 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
?
?
?
?
?
?
3.源碼
3.1 非遞歸
#include <stdio.h>#define N 1024 #define max(x, y) (x > y ? x : y) #define min(x, y) (x < y ? x : y)void knapsack(int *w, int *v, int c, int n, int (*m)[N]); void traceback(int *w, int (*m)[N], int *x, int n, int c);int main(int argc, const char *argv[]) {int n, c, w[N], v[N], m[N][N], x[N], i;scanf("%d%d", &n, &c);for(i = 0; i < n; i++)scanf("%d", &w[i]);for(i = 0; i < n; i++)scanf("%d", &v[i]);knapsack(w, v, c, n-1, m); //這里傳的是數(shù)組的最大下標(biāo)traceback(w, m, x, n-1, c); //求出是否裝載的序列 x[i]printf("Max v: %d\n", m[0][c]);for(i = 0; i < n; i++)printf("x[%d] = %d\t", i, x[i]);printf("\n");return 0; }void knapsack(int *w, int *v, int c, int n, int (*m)[N]){int j, jMax, i;jMax = min(w[n] - 1, c);for(j = 0; j <= jMax; j++) //求m[n][]m[n][j] = 0;for(j = c; j > jMax; j--)m[n][j] = v[n];for(i = n-1; i > 0; i--){ //依次求m[n-1][] - m[1]jMax = min(w[i]-1, c);for(j = 0; j <= jMax; j++){m[i][j] = m[i+1][j];}for(j = c; j > jMax; j--){m[i][j] = max(m[i+1][j], m[i+1][j-w[i]] + v[i]);}}m[0][c] = m[1][c]; //求m[0][]if(w[0] < c){m[0][c] = max(m[1][c], m[1][c-w[0]] + v[0]);} }void traceback(int *w, int (*m)[N], int *x, int n, int c){int i;for(i = 0; i < n-1; i++){if(m[i][c] == m[i+1][c]) //根據(jù)m數(shù)組 判斷是否裝進(jìn)去x[i] = 0;else{x[i] = 1;c -= w[i];}}x[n] = (m[n][c] > 0) ? 1 : 0; }3.2 遞歸
#include <stdio.h> #include <stdlib.h> #include <string.h>#define N 5 #define C 10int w[] = {2, 2, 6, 5,4}; //使用遞歸 避免傳遞太多參數(shù),不清晰 int v[] = {6, 3, 5, 4,6}; //因此設(shè)置成全局變量 int m[N][C], x[N];int knapsack(int i, int j); void traceback();int main(int argc, const char *argv[]) {int i;knapsack(0, C);traceback();printf("Max :%d\n", m[0][10]);for(i = 0; i < N; i++)printf("x[%d] = %d\t", i, x[i]);printf("\n");return 0; }int knapsack(int i, int j){if(i == N-1){m[i][j] = (j > w[i] ? v[i] : 0);return m[i][j];}int ret1, ret2;if(j < w[i]){m[i][j] = knapsack(i+1, j);}else{ret1 = knapsack(i+1, j);ret2 = knapsack(i+1, j-w[i]) + v[i];m[i][j] = ret1 > ret2 ? ret1 : ret2;}return m[i][j];}void traceback(){int i, n = N-1, c = C;for(i = 0; i < n; i++){if(m[i][c] == m[i+1][c]) //根據(jù)m數(shù)組 判斷是否裝進(jìn)去x[i] = 0;else{x[i] = 1;c -= w[i];}}x[n] = (m[n][c] > 0) ? 1 : 0; }3.3 書上有種改進(jìn)的算法,采用跳躍點(diǎn)實(shí)現(xiàn),暫時(shí)還沒看懂,也許過兩天在看就懂了呢。
?
轉(zhuǎn)載于:https://www.cnblogs.com/monicalee/p/4012180.html
總結(jié)
以上是生活随笔為你收集整理的1008-----算法笔记----------0-1背包问题(动态规划求解)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CentOS 6.5 初始值
- 下一篇: 高行健---江西赣州人