C++动态规划------ 0-1背包问题--最长单调递增子序列

文章介绍了如何使用动态规划方法解决0-1背包问题和计算最长递增子序列。对于0-1背包问题,提供了两种不同的代码实现,一种是基于递推数组,另一种是基于状态转移方程。对于最长递增子序列,同样给出了动态规划的解决方案,通过维护以每个元素为结尾的最长递增子序列的长度来找到全局最长的子序列。
摘要由CSDN通过智能技术生成

4.5 01背包问题

 

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 计算01背包问题的最优解

代码:

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;
}


最多拦截导弹

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

captain_dong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值