1.问题描述
给定n种物品和一个背包,物品i的重量是wi,其价值为vi,背包的容量为C。问:应该如何选择装入背包的物品,使得装入背包中物品的总价值最大?
2.问题分析
上述问题可以抽象为一个整数规划问题,即求满足 (a)Σwixi ≤ C;(b)xi ∈(0,1), 1≤i≤n;条件下,∑vixi最大时的一个物品xi序列。分析问题可以发现,该问题具有最优子结构性质,那么就可以尝试用动态规划方法求解,而动态规划求解的关键就是列出问题的递归关系表达式。
设m(i,j)为背包容量为j,可选物品为i,i+1,...n时0-1背包问题的最优质,那么可有如下递归式:
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),此时问题就转化为填充m数组的问题了,以n = 5, c = 10, w[] = {2,2,6,5,4},v[] = {6,3,5,4,6},填充的过程如下图所所示,主要是用上述递归式求值,考虑当前物品能否放入,放入当前物品和不放入导致最终的价值哪个大,图中阴影部分为回溯求xi的过程,表示0,1,4号物品被放入背包中。
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); //这里传的是数组的最大下标 traceback(w, m, x, n-1, c); //求出是否装载的序列 x[i] printf("Max v: %d ", m[0][c]); for(i = 0; i < n; i++) printf("x[%d] = %d ", i, x[i]); printf(" "); 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]) //根据m数组 判断是否装进去 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 10 int w[] = {2, 2, 6, 5,4}; //使用递归 避免传递太多参数,不清晰 int v[] = {6, 3, 5, 4,6}; //因此设置成全局变量 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 ", m[0][10]); for(i = 0; i < N; i++) printf("x[%d] = %d ", i, x[i]); printf(" "); 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]) //根据m数组 判断是否装进去 x[i] = 0; else{ x[i] = 1; c -= w[i]; } } x[n] = (m[n][c] > 0) ? 1 : 0; }
3.3 书上有种改进的算法,采用跳跃点实现,暂时还没看懂,也许过两天在看就懂了呢。