0-1背包问题与数组分割问题

来源:互联网 发布:手机淘宝网银支付 编辑:程序博客网 时间:2024/06/11 16:46

什么是0-1背包问题?

给定两个数组weight[N]和value[N]分别表示物品的重量和价值,现在有一个容量为s的背包,要求在背包中放置若干件物品使得物品的总价值最大。

怎么解决?

采用动态规划,令dp[i][j]表示在前i件产品中做选择且背包中物品重量小于等于j时所选择的物品的最大价值,显然第i件物品的选还是不选完全取决于之前的状态,状态转移方程为dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]),如果dp[i][j] == dp[i-1][j]说明不选择第i件物品,背包中物品的重量仍然为j;如果dp[i][j] == dp[i-1][j-weight[i]] + value[i]说明选择第i件物品,背包重量增加了weight[i]且物品总价值增加了value[i]。最终的返回值为dp[N][s]。

没有进行空间优化的代码:

int knapsackSolve1(const vector<int>& weight, const vector<int>& value, int s){if (weight.size() != value.size()){return 0;}int n = weight.size();vector<vector<int> > dp(n, vector<int>(s + 1, 0));for (int i = 1; i < n; ++i){for (int j = weight[i]; j <= s; ++j){if (dp[i - 1][j - weight[i]] + value[i] > dp[i - 1][j])dp[i][j] = dp[i - 1][j - weight[i]] + value[i];elsedp[i][j] = dp[i - 1][j];}}return dp[n - 1][s];}

进行空间优化的代码:

int knapsackSolve2(const vector<int>& weight, const vector<int>& value, int s){if (weight.size() != value.size()){return 0;}int n = weight.size();vector<int> dp(s + 1, 0);for (int i = 1; i < n; ++i){for (int j = s; j >= weight[i]; --j)//可以节省空间,j必须从大到小{//dp[j]是本轮的,dp[j - weight[i]]是上一轮的,所以必须保证dp[j]在dp[j - weight[i]]之前更新if (dp[j - weight[i]] + value[i] > dp[j]){dp[j] = dp[j - weight[i]] + value[i];}}}return dp[s];}

用背包思想解决数组分割问题:

有一个无序的、元素个数为2n的正整数数组,要求把这个数组分成两个长度为n的子数组,并使两个子数组的和最接近。
用dp[i][j][c]来表示前面i个元素取j个且这j个元素和不大于c(可以认为是背包的容量)的最佳方案(也就是最接近c的方案)。如果第i个元素不取,那么dp[i][j][c] = dp[i-1][j][c];如果第i个元素被选取,那么dp[i][j][c] = dp[i-1][j-1][c-a[i]]+a[i],因此可知动态规划的递推式为dp[i][j][c] = max(dp[i-1][j-1][c-a[i]]+a[i], dp[i-1][j][c])。

代码已经进行空间优化

int divideArray(const vector<int>& array)//已经去除最外围空间{int n;if (array.size() % 2 != 0){return 0;}else{n = array.size() / 2;}int sum = std::accumulate(array.begin(), array.end(), 0);vector<vector<int>> dp(n + 1, vector<int>(sum / 2 + 2, 0));for (int i = 1; i < array.size(); ++i){for (int j = 1; j <= i && j <= n; ++j)//最多只需要选取n个元素{for (int c = sum / 2 + 1; c >= array[i]; --c)//从大到小,为了节约最外围空间{dp[j][c] = max(dp[j - 1][c - array[i]] + array[i], dp[j][c]);}}}return dp[n][sum / 2 + 1];}

关于数组分割问题《编程之美》上有一个更好的解决方案:

维护一个二维数组,用来存储array子数组和,isOk[j][s]这个bool类型的数组表示array中是否存在j个数其和等于s。通过动态规划求出isOk[n][i]=true其中i是小于sum / 2 + 1的最大的整数。

代码如下

int divideArray2(const vector<int>& array){int n;if (array.size() % 2 != 0){return 0;}else{n = array.size() / 2;}int sum = std::accumulate(array.begin(), array.end(), 0);vector<vector<bool>> isOk(n + 1, vector<bool>(sum / 2 + 2, 0));isOk[0][0] = true;//选取0个元素其和为0这种情况肯定为真for (int i = 0; i < array.size(); ++i){for (int j = 1; j <= n && j <= i; ++j){for (int s = sum / 2 + 1; s >= array[i]; --s){if (isOk[j-1][s - array[i]]){isOk[j][s] = true;}}}}//选出最接近sum / 2 + 1的和for (int s = sum / 2 + 1; s >= 0; --s){if (isOk[n][s]){return s;}}return 0;}
上面的两种方法都求出了数组分割后子数组的和,但是子数组具体包含哪些元素并不能知道,下面的代码采用深度优先搜索的方法求数组中的N个元素且它们的和为定值。

//从array中获取和为sum且元素个数为n的子序列(子序列不要求连续)bool fixedSumNSequence(const vector<int>& array, int start, int n, int sum, vector<int>& subArray){if (subArray.size() >= n){if (sum == 0){return true;}else{return false;}}for (int i = start; i < array.size(); ++i){subArray.push_back(array[i]);if (fixedSumNSequence(array, i + 1, n, sum - array[i], subArray)){return true;}subArray.pop_back();}return false;}bool fixedSumNSequence(const vector<int>& array, int n, int sum, vector<int>& subArray){if (n >= array.size()){return false;}return fixedSumNSequence(array, 0, n, sum, subArray);}

测试用例:

int main(){vector<int> weight = { 0, 10, 20, 30 };//在数组前面加上0元素是为了简便代码(动态规划的边界好确定)vector<int> value = { 0, 60, 100, 120 };cout << knapsackSolve1(weight, value, 50) << endl;cout << knapsackSolve2(weight, value, 50) << endl;vector<int> array = { 0, 1, 5, 7, 8, 9, 6, 3, 11, 20, 17, 12 };cout << divideArray(array) << endl;vector<int> subArray;if (fixedSumNSequence(array, array.size() / 2, divideArray2(array), subArray)){for (auto p : subArray){cout << p << "  ";}cout << endl;}return 0;}


1 0
原创粉丝点击