• 初学动态规划--01背包


    动态规划的算法思想应用是十分广泛的,是非常常用的一种算法。

    给出动态规划的抽象描述:

    一个系统,有若干个状态,每个状态下有若干个操作,称为决策,决策会改变系统状态。决策会带来收益(正数)和费用(负数)。在初始状态下,求最终状态下最大收益。在每个阶段,选择一些决策,状态随之改变。收益只取决于当前状态和决策(无后效性)。当系统到达终点状态时,总收益最大(或费用最小)。总收益一般指各个阶段收益的总和。

    动态规划的特点是:子问题大量重叠

    应对:打表避免重复计算(牺牲空间换取时间)


    说到这里对于DP我们的理解肯定还是抽象的,所以来点具体的例子作以说明,对于动态规划初学的例子,我们就来谈谈01背包

    给出01背包的题目描述:

    输入商品数目N,接着输入N个商品的重量和价值,输入一个限定重量,问在这一重量内,能达到的最大价值是多少?

    我们可以分析一下,每准备放入一个物品的时候我们需要判断此时还能否放的下,如果可以放下,放下这个的情况和不放这个的情况哪个可以获得更大的收益(即在一定的重量内可以获得最大的价值),经过递归我们读出最后的数据。

    给出根据算法思想写出的代码:

    int rec(int i, int j){
    	int res;
    	if(i == n){
    		res = 0;
    	}
    	else if(j < w[i]){
    		res = rec(i + 1, j);
    	}
    	else{
    		res = max(rec(i + 1, j) , rec(i + 1, j - w[i]) + v[i]);
    	}
    	return res;
    }

    我们可以简单研究一下该算法的时间复杂度,我们发现,这个代码写出来的方法需要执行很多重复的情况.举例来说,给出一下输入数据:

    4

    2 3

    1 2

    3 4

    2 2

    5

    那么在输入时就有重复的(3,2)状态,出现在前两个递归不取物品,第三个递归取物品时与第一个递归取物品,第二个递归继续取物品,这时无法取第三个物品,所以组成(3,2)状态.所以这种方法的搜索深度是n,而且每一层的搜索搜需要两次分支,最坏需要O(2^N),当N比较大的时候就可能超市了

    那么为了解决这种问题我们该怎么办呢,就用到我们上面抽象描述中给出的方法,也就是打表,以消耗空间的方式来换取时间.我们需要一个二维矩阵dp[MAX_N][MAX_N],借用这个矩阵对每个状态进行记忆,如果再遇到这种状态时,立即返回这种状态的值即可,就没必要继续递归下去.给出代码:

    int rec(int i, int j){
    	int res;
    	if( dp[i][j] ){
    		return dp[i][j];                                                                  //如果该状态记忆矩阵中有记录的话,我们将此返回
    	}
    	if(i == n)
    		res = 0;
    	else if(j < w[i])
    		res = rec(i + 1, j);
    	else{
    		res = max(rec(i + 1, j), rec(i + 1, j - w[i]) + v[i]);
    	}
    	return dp[i][j] = res;                                                                 //对该状态进行记忆
    }

    我们发现这张对于同样的参数,只会在第一次被调用时需要执行递归部分,第二次后直接返回,参数的组合不过N*W种,而函数只调用两次递归,所以只需要O(N*W)

    当然我们容易知道,dp的形式有很多很多,我们可以通过二维数组直接对我们的dp数组进行填表,我们将数组初始化后,从下往上填充,然后我们可以知道,最后第0行的第w行为放入最大价值的情况


    我们常说的递归(DP)就是这样:

    int solve(){
    	int i, j;
    	for(i = n - 1; i >= 0; i --){
    		for(j = 0; j <= W; j ++){
    			if(j < w[i]){
    				dp[i][j] = 0;
    			}
    			else {
    				dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]);
    			}
    		}
    	}
    }
    我们一般在做DP时需要给出动态转移方程:

    如上面的动态转移方程我们可以写作:

    dp[n][j] = 0;

    dp[i][j] = ①dp[i + 1][j] (j < w[i])

          ②max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i])

    动态转移方程是我们写出代码的思想基础.

  • 相关阅读:
    一道C#基础题,看你能多长时间做出来?
    终于能在这里安家了
    你知道返回多少吗?(使用Math类)
    关于implicit和explicit关键词的用法
    关于基类与派生类的学习
    js控制输入框
    Oracle 动态SQL返回单条结果和结果集 转帖
    定时器:.NET Framework类库中的Timer类比较(转帖)
    UVA10020 Minimal coverage
    UVA1388 Graveyard
  • 原文地址:https://www.cnblogs.com/chilumanxi/p/5136136.html
Copyright © 2020-2023  润新知