暴力枚举、搜索、动态规划

从下面这道题引入

四种面值的货币: 1 , 5 , 10 , 25 1, 5, 10, 25 1,5,10,25,每种均有无数张可用,给定一个整数 a i m aim aim 表示要凑齐的金额,计算有多少种方法可以凑齐?

可以看出,上面的题目是一个完全背包问题,我们尝试用六种方法来对它进行求解,来体会不同方法的差异,了解动态规划的推导

暴力枚举

最简单的一种方法,用循环来枚举每种货币的数目。因为比较低效,当货币面值太多或者要凑齐的金额很大时,就不便使用这种方法。

时间复杂度: O ( a i m 4 ) O(aim^4) O(aim4)

#include <iostream>
using namespace std;

int v[] = {1, 5, 10, 25};

int main(void)
{
	int res = 0, aim;
	cin >> aim;
	for (int i = 0; i <= aim / v[0]; i++)
		for (int j = 0; j <= aim / v[1]; j++)
			for (int k = 0; k <= aim / v[2]; k++)
				for (int h = 0; h <= aim / v[3]; h++)
					if (i * v[0] + j * v[1] + k * v[2] + h * v[3] == aim)
						res++;
						
	cout << res << endl;
	
	return 0;
}

深度优先搜索(无返回值)

暴力搜

1)如果能取到目标金额 a i m aim aim,就使全局变量 r e s res res 1 1 1 并返回

2)如果不能取到目标金额,就直接返回

3)否则就分两种情况:取当前面值货币 v [ u ] v[u] v[u] 或跳入下一层 u + 1 u+1 u+1,进行递归求解。

时间复杂度: O ( a i m 4 ) O(aim^4) O(aim4)

#include <iostream>
using namespace std;

int v[] = {1, 5, 10, 25};
int res, aim;

//u表示当前的层数,state表示当前凑到的金额
void dfs(int u, int state)
{
	if (state == aim) {
		res++;
		return;
	}
	if (u == 4 || state > aim) {
		return;
	}
	//取当前面值货币 
	dfs(u, state + v[u]);
	//跳入下一层
	dfs(u + 1, state);
}

int main(void)
{
	cin >> aim;
	
	dfs(0, 0);
	cout << res << endl;
	
	return 0;
}

深度优先搜索(有返回值)

每次利用一个 r e s res res 来存储当前的值

{ r e s = 1 s t a t e = a i m r e s = 0 u = 4   ∣ ∣   s t a t e > a i m r e s = d f s ( u , s t a t e + v [ u ] ) + d f s ( u + 1 , s t a t e ) 其 他 \begin{cases} res=1&state = aim\\ res=0&u=4~||~state>aim\\ res=dfs(u, state + v[u]) + dfs(u + 1, state)&其他 \end{cases} res=1res=0res=dfs(u,state+v[u])+dfs(u+1,state)state=aimu=4  state>aim

1)如果能取到目标金额 a i m aim aim,局部变量 r e s = 1 res=1 res=1 并返回

2)如果不能取到目标金额,局部变量 r e s = 0 res=0 res=0 并返回

3)否则就拆分为两种情况:局部变量 r e s = res= res= 取当前面值货币的方法数 + + + 取下一面值货币的方法数,即 d f s ( u , s t a t e + v [ u ] ) + d f s ( u + 1 , s t a t e ) dfs(u, state + v[u]) + dfs(u + 1, state) dfs(u,state+v[u])+dfs(u+1,state),进行递归求解。

最后返回 r e s res res 即可。

时间复杂度: O ( a i m 4 ) O(aim^4) O(aim4)

#include <iostream>
using namespace std;

int v[] = {1, 5, 10, 25};
int aim;

//u表示当前的层数,state表示当前凑到的金额
int dfs(int u, int state)
{
	int res = 0;
	if (state == aim) {
		res = 1;
	} else if (u == 4 || state > aim) {
		res = 0;
	} else {
		//取当前面值货币的方法数 + 取下一面值货币的方法数
		res = dfs(u, state + v[u]) + dfs(u + 1, state);
	}
	
	return res;
}

int main(void)
{
	cin >> aim;
	
	cout << dfs(0, 0) << endl;
	
	return 0;
}

深度优先搜索(记忆化)

会发现每次在计算 d f s ( u , s t a t e ) dfs(u, state) dfs(u,state) 时,会有很多次重复的计算,比如取了 10 10 10 张一元和 0 0 0 张五元时,此时需要计算 d f s ( 2 , 10 ) dfs(2, 10) dfs(2,10),之后取了 5 5 5 张一元和 1 1 1 张五元时需要再次计算 d f s ( 2 , 10 ) dfs(2, 10) dfs(2,10)

此时,可以用一个 d p dp dp 矩阵来记录每种情况是否被计算过,用空间来换时间。

如果 d f s ( u , s t a t e ) dfs(u, state) dfs(u,state) 没有被计算过,就进行计算并将数值记录在 d p [ u ] [ s t a t e ] dp[u][state] dp[u][state] 中。如果被计算过,就直接使用 d p [ u ] [ s t a t e ] dp[u][state] dp[u][state] 即可。

因为情况只有 4 ∗ a i m 4*aim 4aim 种,只需要计算 4 ∗ a i m 4*aim 4aim 次,所以时间复杂度可以优化到 O ( 4 ∗ a i m ) O(4*aim) O(4aim)

#include <iostream>
using namespace std;

int v[] = {1, 5, 10, 25};
int aim;
int dp[5][1005];

int dfs(int u, int state)
{
	//如果已经计算过,直接返回即可 
	if (dp[u][state] != 0) {
		return dp[u][state];
	}
	
	int res = 0;
	if (state == aim) {
		res = 1;
	} else if (u == 4 || state > aim) {
		res = 0;
	} else {
		res = dfs(u, state + v[u]) + dfs(u + 1, state);
	}
	
	return dp[u][state] = res;
}

int main(void)
{
	cin >> aim;
	
	cout << dfs(0, 0) << endl;
	
	return 0;
}

动态规划(二维)

直接对 d p dp dp 矩阵进行操作,通过矩阵来进行状态转移

d p [ i ] [ j ] : 表 示 使 用 前 i 种 面 值 的 货 币 来 凑 出 金 额 j 的 方 法 数 dp[i][j]:表示使用前i种面值的货币来凑出金额j的方法数 dp[i][j]使ij

状态转移方程:

d p [ i + 1 ] [ j ] = { d p [ i ] [ j ] j < v [ i ] d p [ i ] [ j ] + d p [ i + 1 ] [ j − v [ i ] ] j ≥ v [ i ] dp[i+1][j]=\begin{cases} dp[i][j]&j<v[i]\\ dp[i][j]+dp[i+1][j-v[i]]&j≥v[i] \end{cases} dp[i+1][j]={dp[i][j]dp[i][j]+dp[i+1][jv[i]]j<v[i]jv[i]

1)如果 j < v [ i ] j<v[i] j<v[i],表示当前需要的金额 j j j 小于当前面值的货币 v [ i ] v[i] v[i],此时不能取当前面值货币,所以只能由 d p [ i ] [ j ] dp[i][j] dp[i][j] 转移而来。

2)如果 j ≥ v [ i ] j\geq v[i] jv[i],表示当前需要的金额 j j j 大于等于当前面值的货币 v [ i ] v[i] v[i],此时可以不取当前面值货币也可以取当前面值货币,所以可以由 d p [ i ] [ j ] dp[i][j] dp[i][j] d p [ i + 1 ] [ j − v [ i ] ] dp[i+1][j-v[i]] dp[i+1][jv[i]] 转移而来。

矩阵的最后一个元素 d p [ 4 ] [ a i m ] dp[4][aim] dp[4][aim]:表示用前 4 4 4 种货币来凑出金额 j j j ,就是最后的答案。

因为一共有 4 4 4 种货币,目标金额为 j j j ,所以 d p dp dp 矩阵大小是 4 × a i m 4×aim 4×aim。又因为求解过程就是求这个矩阵,所以时间复杂度为: O ( 4 ∗ a i m ) O(4*aim) O(4aim)

#include <iostream>
using namespace std;

int v[] = {1, 5, 10, 25};
int aim;
int dp[5][1005];

int main(void)
{
	cin >> aim;
	dp[0][0] = 1;
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j <= aim; j++) {
			if (j < v[i])
				dp[i + 1][j] = dp[i][j];
			else
				dp[i + 1][j] = dp[i][j] + dp[i + 1][j - v[i]];
		}
	}
	
	cout << dp[4][aim] << endl;
	
	return 0;
}

动态规划(一维)

已知时间复杂度和空间复杂度均为 O ( 4 ∗ a i m ) O(4*aim) O(4aim),时间复杂度应该不能再优化了,但空间复杂度可以进一步的优化到 O ( a i m ) O(aim) O(aim)

可以丢弃掉物品维度,使用一维的 d p [ 0... a i m ] dp[0...aim] dp[0...aim] 数组来存储对应的值

关于一维优化的详解:https://blog.csdn.net/weixin_43772166/article/details/99487551

#include <iostream>
using namespace std;

int v[] = {1, 5, 10, 25};
int aim;
int dp[1005];

int main(void)
{
	cin >> aim;
	dp[0] = 1;
	for (int i = 0; i < 4; i++)
		for (int j = v[i]; j <= aim; j++)
			dp[j] = dp[j] + dp[j - v[i]];

	cout << dp[aim] << endl;
	
	return 0;
}
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值