4.5 0-1背包问题
4.5.1 递归关系分析
代码:
#define NUM 50 //物品数量的上限
#define CAP 1500 //背包容量的上限
int w[NUM]; //物品的重量
int v[NUM]; //物品的价值
int p[NUM][CAP]; //用于递归的数组
//形参c是背包的容量W,n是物品的数量
void knapsack(int c, int n)
{
//计算递推边界
int jMax=min(w[n]-1,c); //分界点
for( int j=0; j<=jMax; j++) p[n][j]=0;
for( int j=w[n]; j<=c; j++) p[n][j]=v[n];
for( int i=n-1; i>1; i--) //计算递推式
{
jMax=min(w[i]-1,c);
for( int j=0; j<=jMax; j++)
p[i][j]=p[i+1][j];
for(int j=w[i]; j<=c; j++)
p[i][j]=max(p[i+1][j], p[i+1][j-w[i]]+v[i]);
}
p[1][c]=p[2][c]; //计算最优值
if (c>=w[1]) p[1][c]=max(p[1][c], p[2][c-w[1]]+v[1]);
}
算法4.10 计算0-1背包问题的最优解
代码:
void traceback( int c, int n, int x[ ])
{
for(int i=1; i<n; i++)
{
if (p[i][c]==p[i+1][c]) x[i]=0;
else { x[i]=1; c-=w[i]; }
}
x[n]=(p[n][c])? 1:0;
}
n | j | 0 | 1 | 2 | 3 | 4 | 5 |
1 | 0 | 0 | 0 | 0 | 0 | 37 |
2 | 0 | 10 | 15 | 25 | 30 | 35 |
3 | 0 | 0 | 15 | 20 | 20 | 35 |
4 | 0 | 0 | 15 | 15 | 15 | 15 |
W=5 | 1 | 2 | 3 | 4 |
重量w | 2 | 1 | 3 | 2 |
价值v | 12 | 10 | 20 | 15 |
最优解x | 1 | 1 | 0 | 1 |
实现代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int NUM = 50;//物品数量上限
const int CAP = 1500;//背包容量上限
int w[NUM] = { 2, 1, 3, 2 };
int v[NUM] = { 12, 10, 20, 15 };
int p[NUM][CAP];
void knapsack(int w[], int v[], int c, int n) {
int jMax = min(w[n - 1] - 1, c);
for (int j = 0; j <= jMax; j++)
p[n - 1][j] = 0;
for (int j = w[n - 1]; j <= c; j++)
p[n - 1][j] = v[n - 1];
for (int i = n - 2; i >= 0; i--) {
jMax = min(w[i] - 1, c);
for (int j = 0; j <= jMax; j++)
p[i][j] = p[i + 1][j];
for (int j = w[i]; j <= c; j++)
p[i][j] = max(p[i + 1][j], p[i + 1][j - w[i]] + v[i]);
}
cout << p[0][c] << endl;
}
int main() {
int c = 5;
knapsack(w, v, c, sizeof(w) / sizeof(w[0]));
return 0;
}
运行结果:
#include <iostream>
using namespace std;
const int MAXN = 105; // 物品数目的最大值
const int MAXW = 1005; // 背包容量的最大值
int main() {
int n, W;//n 表示物品的数量,w:总重量。
int w[MAXN], v[MAXN];
int dp[MAXW] = { 0 }; // dp[i]表示载重量为i时,能够获得的最大价值
cin >> n >> W;
for (int i = 1; i <= n; i++) {
cin >> w[i] >> v[i];
}
for (int i = 1; i <= n; i++) {
for (int j = W; j >= w[i]; j--) { // 注意:倒序更新,避免重复选取物品
dp[j] = max(dp[j - w[i]] + v[i], dp[j]); // 状态转移方程
}
}
cout << dp[W] << endl; // 输出载重量为W时的最大价值
return 0;
}
首先输入 n、W 和每个物品的重量和价值,然后初始化 dp 数组为 0,表示载重量为 0 时,能够获得的最大价值为 0。接着使用两层循环,枚举每个物品,以及各种载重量下的最大价值。在每次状态转移时,使用一个 max 函数更新 dp 数组的值,状态转移方程为
dp[j] = max{dp[j-w[i]]+v[i], dp[j]}
(其中 j ∈ [w[i], W]),表示考虑选择第 i 个物品和不选择第 i 个物品两种方案,取价值最大的那一个。注意:在更新 dp 数组时,需要倒序更新,避免重复选取物品。最后输出载重量为 W 时的最大价值即可。时间复杂度为 O(nW)。
#include<iostream>
#include<algorithm>
using namespace std;
int f[5][9] = { 0 };
int w[5] = { 0,2,3,4,5 };
int v[5] = { 0,3,4,5,8 };
int main() {
int i, j;
memset(f, 0, sizeof(f));
for (i = 1; i < 5; i++)
for (j = 1; j < 9; j++) {
if (w[i] > j)
f[i][j] = f[i - 1][j];
else
f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[i]);
}
for (i = 1; i < 5; i++) {
for (j = 1; j < 9; j++) {
cout <<"f["<<i<<"]"<<"[" << j << "]"<<"="<< f[i][j] << "\t";
if (j % 8 == 0) {
cout << "\n"; // 每5个结果换行
}
}
}
cout << endl;
return 0;
}
运行结果:
4.6 最长单调递增子序列
算法4.11 计算最长递增子序列的动态规划算法
#define NUM 100
int a[NUM]; //序列L
int LIS_n2(int n) //教材上这一句丢掉了
{
int b[NUM]={0}; //辅助数组b
int i,j;
b[1] = 1;
int max = 0; //数组b的最大值
for (i=2;i<=n; i++)
{
int k = 0;
for (j=1; j<i; j++) //0~i-1之间,b的最大值
if (a[j]<=a[i] && k<b[j]) k=b[j];
b[i] = k+1;
if (max<b[i]) max=b[i];
}
return max;
}
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
数组a | 65 | 158 | 170 | 155 | 239 | 300 | 207 | 389 |
数组b | 1 | 2 | 3 | 2 | 4 | 5 | 4 | 6 |
完整代码:
#include <iostream>
using namespace std;
const int MAXN = 105; // 数组最大长度
int main() {
int n;
int a[MAXN];
int dp[MAXN]; // dp[i]表示以a[i]为结尾的最长递增子序列的长度
int maxLen = 0; // 记录最长递增子序列长度
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
dp[i] = 1; // 初始化dp数组
}
for (int i = 1; i < n; i++) { // 枚举结尾元素
for (int j = 0; j < i; j++) { // 枚举前面的元素
if (a[j] < a[i]) {
dp[i] = max(dp[i], dp[j] + 1); // 状态转移方程
}
}
maxLen = max(maxLen, dp[i]); // 更新最大长度
}
cout << maxLen << endl;
return 0;
}
首先输入 n 和数组元素,然后初始化 dp 数组为 1,表示每个元素自身构成一个递增子序列。接着使用两层循环,枚举以第 i 个元素为结尾的最长递增子序列的长度,状态转移方程为
dp[i] = max{dp[j]+1}
(其中 j ∈ [0, i-1],a[j] < a[i]),表示考虑以第 j 个元素为结尾的最长递增子序列长度加上第 i 个元素后能够构成的最长递增子序列长度。然后在每次状态转移后,更新最大长度。最后输出最长递增子序列的长度即可。时间复杂度为 O(n^2)。
#include<iostream>
#include<algorithm>
using namespace std;
int a[100];//表示给定的序列
int dp[100];//用来储存当前位置作为结尾的最长上升子序列的长度
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++) {
dp[i] = 1;
for (int j = 1; j < i; j++) {
if (a[j] < a[i])
dp[i] = max(dp[i], dp[j] + 1);
}
cout<< dp[i]<<endl;
}
return 0;
}
运行结果:
#include<iostream>
#include<algorithm>
using namespace std;
int a[100];
int dp[100];
int pre[100];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
int lis_max_index = 1; // 最长上升子序列的结尾位置
for (int i = 1; i <= n; i++) {
dp[i] = 1;
pre[i] = -1;
for (int j = 1; j < i; j++) {
if (a[j] < a[i] && dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
pre[i] = j;
}
}
if (dp[i] > dp[lis_max_index])
lis_max_index = i;
}
cout << "最长上升子序列的长度为:" << dp[lis_max_index] << endl;
cout << "最长上升子序列为:";
for (int i = lis_max_index; i != -1; i = pre[i])
cout << a[i] << " ";
return 0;
}
最多拦截导弹数