动态规划算法(斐波那契数列,剪绳子问题)

记录学习的动态规划算法。
1.理论理解:
基本思想
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式
个人理解:将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案。
2.动态规划运用实现
2.1实现斐波那契数列
斐波那契数列:0,1,1,2,3,5,8,13,21…使用数学归纳法可以得出规律为:
f(0)=0;
f(1)=1;
f(n) = f(n-1) + f(n-2)。(n>2)
一般大家都用递归方法实现:

 public int fib (int n) {
       if (n < 2) return n;
		return fib(n - 1) + fib(n - 2);
    }

程序通过递归调用代码简单明了,为什么Dynamic Programming这么复杂而又难理解的写法?我一开始也是疑惑,但是当我看到这个例子,当程序递归执行fib(4)时的过程:
在这里插入图片描述

从图中可以看到,因为递归进行了太多重复计算,但是当n比较大的时候,这种重复计算就不得了了。计算机的语言描述就是时间复杂度很高。量化的话就是O(2^n)。
所以对于计算过的数值,我们可以按照动态规划的思想先保存下来,用到的时候直接调用,避免重复计算:

//第一种用数组先保存结果
public int CFib(int n) {
    	//声明一个长度为n+1的数组用来存放每一位算出的数值
        int dp[] = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i <= n; i++)
          dp[i] = dp[i - 1] + dp[i - 2];
        return dp[n];
        }
//第二种:声明两个变量存放一下次计算需要用到的值
public int CFib2(int n) {
        if (n < 2)
          return n;
        int n1 = 0, n2 = 1, temp;
        for (int i = 2; i <= n; i++) {
          //先将n1,n2的和保存下来
          temp = n1 + n2;
          //将n2的值赋予n1
          n1 = n2;
          //将n1,n2的和赋予n2;这样n1和n2总是存放了下次计算需要用到的值
          n2 = temp;
        }
        return n2;
      }

程序自下向上执行,这个时候执行过程就变成了如图,时间复杂度变成了O(n),避免了重复计算:
在这里插入图片描述
2.2解决剪绳子问题
在我的这一篇贪心算法解决剪绳子问题,我已经用贪心算法解决了剪绳子问题,但是动态规划性能更加优化:
题目描述
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]xk[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
输入描述:
输入一个数n,意义见题面。(2 <= n <= 60)
输出描述:
输出答案。
示例1
输入

8

输出

18

动态规划实现

/*
	 * 题意:一段长度大于2的绳子,分成不能少于2段的绳子,求分后的绳子乘积最大值。 按照贪心算法,每次按照局部最大求解。 length=2, max=1*1=1
	 * length=3, max=1*2=2 比4小的数差分下来做乘积都比原来的数小。 拆分4是个临界值:4=2*2。 length=4, max=2*2=4
	 * 拆分5时,5<max=2*3=6,出现拆分后的数的乘积大于原来的数。
	 * 我们尽可能多的剪长度为3的绳子:当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子。 关键:length=5,max=2*3=6 length=6,
	 * max=3*3=9 length=7, max=3*4=12 length=8, max=3*3*2=18 ...
	 */
	public int cutRope(Integer length) {
		//绳子在3米以内,直接返回对应的值,不能用动态规划的公式做
		if(length<2)
			return 0;
		if(length==2)
			return 1;
		if(length==3)
			return 2;
		//创建数组存储子问题最优解
		int results[]=new int[length+1];
		//绳子大于3米,可以用动态规划的公式做,f(n)=f(i)*f(n-i)
		//对于绳子长度为1,2,3米的,绳子的最大乘积就是长度本身
		results[0]=0;
		results[1]=1;
		results[2]=2;
		results[3]=3;

		int max=0;
		//从4米开始计算,直到计算到总长
		for(int i=4;i<=length;++i)
		{
			max=0;
			//对于长度为i的绳子,计算所有可能的切分,找到最大值
			//i/2是避免重复计算,比如2*3和3*2是一样的
			for(int j=1;j<=i/2;++j)
			{
				//切分组合,计算所有可能的组合,找到最大值
				int temp=results[j]*results[i-j];
				if(max<temp)
					max=temp;
				results[i]=max;
			}
		}
		max=results[length];
		return max;
	}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值