从暴力递归到动态规划

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wk_bjut_edu_cn/article/details/84928626

动态规划

什么是动态规划方法?

1.其本质是利用申请的空间来记录每一个暴力搜索的结果,下次要用结果的时候就可以直接使用,而不需要在进行重复的递归过程。

2.动态规划规定每一种递归状态的计算顺序,依次进行计算。

1.动态规划算法是从暴力搜索算法优化过来的,如果我们不清楚暴力搜索的过程,就难以理解动态规划的实现,当我们了解了动态规划算法的基本原理的文字概述,实现条件之后,这时可能并不是太理解这种思想,去面对实际问题的时候也是无从下手,这个时候我们不能停留在文字层面上,而应该去学习经典动态规划算法的实现,然后倒回来看这些概念,便会恍然大悟。

2.动态规划算法的难点在于 从实际问题中抽象出动态规划表dp,dp一般是一个数组,可能是一维的也可能是二维的,也可能是其他的数据结构。

3.总体来说,动态规划算法就是一系列以空间换取时间的算法。 
 

换钱的方法数

【题目】给定数组arr,arr中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求换钱有多少种方法。

【举例】arr=[5,10,25,1],aim=0。组成0元的方法有1种,就是所有面值的货币都不用。所以返回1。arr=[5,10,25,1],aim=15。组成15元的方法有6种,分别为3张5元、1张10元+1张5元、1张10元+5张1元、10张1元+1张5元、2张5元+5张1元和15张1元。所以返回6。arr=[3,5],aim=2。任何方法都无法组成2元。所以返回0。

代码:

#include<iostream>
#include<vector>
using namespace std;
//方法一:暴力递归,时间复杂度最差为O(aim^N)
int process(const vector<int> &money, int index, int target);
int coin1(const vector<int> &money, int target)
{
	if (money.empty() || target < 0)
		return 0;
	return process(money, 0, target);
}
//target代表还剩多少钱,index是所有面值的数量
int process(const vector<int> &money, int index, int target)
{
	int res = 0;
	if (index == money.size())
		res = target == 0 ? 1 : 0;
	else
	{
		/*
		比如选0张5元,那么剩下的target是10元,交给3和2元去拼凑
		然后选1张5元,那么剩下的target是5元,交给3和2元去拼凑
		然后选2张5元,那么剩下的target是0元,交给3和2元去拼凑
		对于3元和2元的处理过程,也是类似,所以进行递归
		*/
		for (int i = 0; money[index] * i <= target; ++i)
			res += process(money, index + 1, target - money[index] * i);
	}
	return res;
}
//方法二:动态规划,时间复杂度为O(N×aim)
int coin2(const vector<int> &money, int target)
{
	if (money.empty() || target < 0)
		return 0;
	int m = money.size();
	vector<vector<int>> dp(m, vector<int>(target + 1, 0));

	//第一列表示组成钱数为0的方法数,所以为1
	for (int i = 0; i < m; ++i)
		dp[i][0] = 1;
	//计算二维数组的第一行,对此题来说就是计算只用money[0]
	//价值的钱构成1-target的方法数
	for (int i = 1; money[0] * i <= target; ++i)
		dp[0][money[0] * i] = 1;

	//求一般位置的dp[i][j],由两者叠加
	for (int i = 1; i < m; ++i)
		for (int j = 1; j <= target; ++j)
		{
			dp[i][j] = dp[i - 1][j];//不用money[i]组成j
			dp[i][j] += j - money[i] >= 0 ? dp[i][j - money[i]] : 0;//使用money[i]
		}
	return dp[m - 1][target];
}
int main(void)
{
	vector<int> money = { 3,2,5 };
	int result = coin1(money, 10);
	cout << result << endl;
	system("pause");
	return 0;
}

排成一条线的纸牌博弈问题

【题目】
给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。

【举例】
arr=[1,2,100,4]。
开始时玩家A只能拿走1或4。如果玩家A拿走1,则排列变为[2,100,4],接下来玩家B可以拿走2或4,然后继续轮到玩家A。如果开始时玩家A拿走4,则排列变为[1,2,100],接下来玩家B可以拿走1或100,然后继续轮到玩家A。玩家A作为绝顶聪明的人不会先拿4,因为拿4之后,玩家B将拿走100。所以玩家A会先拿1,让排列变为[2,100,4],接下来玩家B不管怎么选,100都会被玩家A拿走。玩家A会获胜,分数为101。所以返回101。arr=[1,100,2]。开始时玩家A不管拿1还是2,玩家B作为绝顶聪明的人,都会把100拿走。玩家B会获胜,分数为100。所以返回100。

代码:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//暴力递归
int f(vector<int> &v, int i, int j);
int s(vector<int> &v, int i, int j);
int win1(vector<int> &v)
{
	if (v.empty())
		return 0;
	int n = v.size();
	return max(f(v, 0, n - 1), s(v, 0, n - 1));
}
//玩家A先拿
int f(vector<int> &v, int i, int j)
{
	if (i == j)
		return v[i];
	return max(v[i] + s(v, i + 1, j), v[j] + s(v, i, j - 1));
}
//玩家B后拿
int s(vector<int> &v, int i, int j)
{
	if (i == j)
		return 0;
	return min(f(v, i + 1, j), f(v, i, j - 1));
}

//动态规划
int win2(vector<int> &v)
{
	if (v.empty())
		return 0;
	int n = v.size();
	vector<vector<int>> f(n, vector<int>(n, 0));
	vector<vector<int>> s(n, vector<int>(n, 0));
	//以列为标准进行计算,因为是在前一列的基础上计算后一列的
	for (int j = 0; j < n; ++j)
	{
		f[j][j] = v[j];
		for (int i = j - 1; i >= 0; --i)
		{
			f[i][j] = max(v[i] + s[i + 1][j], v[j] + s[i][j - 1]);
			s[i][j] = min(f[i + 1][j], f[i][j - 1]);
		}
	}
	return max(f[0][n - 1], s[0][n - 1]);
}
int main(void)
{
	vector<int> v = { 1,2,3,4,5 };
	int result1 = win1(v);
	int result2 = win2(v);
	cout << result1 << endl;
	cout << result2 << endl;
	system("pause");
	return 0;
}

机器人左右移动

现有一条坐标轴,一个机器人初始停留在 m 位置, 可以走 p 步, 如果在 n 位置只能往左走, 如果在 1 位置只能往右走,问最终停留在 k 上有多少种情况?

代码:

#include<iostream>
#include<vector>
using namespace std;
//暴力递归版本
/*
n:一共有n个位置
m:初始停留的位置
p:可以走的步数
k:最终停留的位置
*/
int ways1(int n, int m, int p, int k)
{
	if (n < 2 || m<1 || m>n || p < 0 || k<1 || k>n)
		return 0;
	//走了p步之后是否在最终的位置
	if (p == 0)
		return m == k ? 1 : 0;
	int res = 0;
	//来到了最左边,只能往右走
	if (m == 1)
		res += ways1(n, m + 1, p - 1, k);
	//来到了最右边,只能往左走
	else if (m == n)
		res += ways1(n, m - 1, p - 1, k);
	//左右都可以走
	else
		res += ways1(n, m + 1, p - 1, k) + ways1(n, m - 1, p - 1, k);
	return res;
}
//动态规划版本
/*
n:一共有n个位置
m:初始停留的位置
p:可以走的步数
k:最终停留的位置
*/
int ways2(int n, int m, int p, int k)
{
	vector<vector<int>> dp(p + 1, vector<int>(n+1, 0));
	dp[0][k] = 1;
	for (int i = 1; i <= p; ++i)
	{
		for (int j = 1; j <= n; ++j)
		{
			if (j == 1)
				dp[i][j] = dp[i - 1][j + 1];
			else if (j == n)
				dp[i][j] = dp[i - 1][j - 1];
			else
				dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
		}
	}
	return dp[p][m];
}

0-1 背包问题

问题:有n个物品,第i个物品价值为vi,重量为wi,其中vi和wi均为非负数,背包的容量为W,W为非负数。现需要考虑如何选择装入背包的物品,使装入背包的物品总价值最大。该问题以形式化描述如下:

       目标函数为 :     

       约束条件为:

       满足约束条件的任一集合(x1,x2,...,xn)是问题的一个可行解,问题的目标是要求问题的一个最优解。考虑一个实例,假设n=5,W=17, 每个物品的价值和重量如表9-1所示。可将物品1,2和5装入背包,背包未满,获得价值22,此时问题解为你(1,1,0,0,1)。也可以将物品4和5装入背包,背包装满,获得价值24,此时解为(0,0,0,1,1)。

下面根据动态规划的4个步骤求解该问题。

(1) 刻画0-1背包问题的最优解的结构。

      可以将背包问题的求解过程看作是进行一系列的决策过程,即决定哪些物品应该放入背包,哪些物品不放入背包。如果一个问题的最优解包含了物品n,即xn=1,那么其余x1,x2,...,x(n-1)一定构成子问题1,2,...,n-1在容量W-wn时的最优解。如果这个最优解不包含物品n,即xn=0,那么其余x1,x2,...,x(n-1)一定构成子问题1,2,...,n-1在容量W时的最优解。

(2)递归定义最优解的值

     根据上述分析的最优解的结构递归地定义问题最优解。设c[i,w]表示背包容量为w时,i个物品导致的最优解的总价值,得到下式。显然要求c[n,w]。

(3)计算背包问题最优解的值


 

未完待续................................

猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/84928626