【动态规划算法】从斐波那契数列谈起

读了这篇文章,让动态规划不再神秘

斐波那契数列

有这样一个有趣的故事:

假设第1个月有1对刚诞生的兔子,第2个月进入成熟期,第3个月开始生育兔子,而1对成熟的兔子每月会生1对兔子,兔子永不死去……那么,由1对初生兔子开始,12个月后会有多少对兔子呢?

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)

斐波那契数列的递归解法

根据上述斐波那契数列的递归定义方法,我们很容易想到用递归的方式来求解斐波那契数列:

int Fibonacci1(int n){
	if (n<3){
		return 1;
	}
	return Fibonacci1(n-1) + Fibonacci1(n-2);
}

我们来分析一下上述过程:
递归求解的过程如同构造一棵二叉树,例如求解 F(5)依赖F(4)和F(3),我们把F(5)作为树的根节点,F(4)和F(3)作为左右两个叶子节点,继续向下递归,左节点F(4)继续向下分解为F(3)和F(2),右节点F(3)继续向下分解为F(2)和F(1),依此类推,如下图所示:
【动态规划算法】从斐波那契数列谈起_第1张图片
可以很直观的看到,该算法的时间复杂度为O(2^N)。N越大,计算的复杂度就越大;并且,每个结果都会被重复计算,如果我们在计算时可以将这些中间结果存储起来,使用的时候直接读取,就不用在计算每个节点的时候都递归到最下层求F(1)和F(2)的值,会节省很多时间。

动态规划

动态规划的基本思想:将过程分为若干个互相联系的阶段,即子问题,将各阶段按照一定的次序排列好之后,对于某个给定的阶段,先求解子问题,然后从这些子问题的解的方法得到原问题的解;对于重复出现 的子问题,只在第一次遇到的时候对他们进行求解,并把答案保存起来,让以后再次遇到时直接引用答案。

动态规划的特点

  • 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
  • 无后效性。即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策影响。
  • 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

动态规划求解斐波那契数列

我们申请一个长为n的数组,用来记录F(n)的值,这样在需要求F(n-1)和F(n-2)的值时,就可以直接读取前面得到的结果了:

int Fibonacci2(int n){
   int *dp = new int [n+1]; // 定义dp数组
   dp[1] = 1; // 初始化
   dp[2] = 1; // 初始化
   for (int i = 3; i <= n; ++i)
   	dp[i] = dp[i - 1] + dp[i - 2]; //迭代求解
   return dp[n];
}

时间复杂度:O(N),空间复杂度:O(N)
以上就是动态规划思路的体现,不用每一次都要从头开始求到当前步骤,而是可以基于前面步骤得到的结果,直接利用上面的结果得到当前想要的答案。
动态规划的关键就是明确dp数组or矩阵中每个元素代表的含义,然后给出初始化条件以及迭代公式,剩下的就是利用公式迭代求解了。

另外,一般的动态规划都有优化存储空间的可能,如果迭代公式里体现出当前的解仅与前面的有限个解有关的话,可以通过覆盖前面的值来实现仅存放于当前结果有关的值,达到节省空间的目的。当然这样的做法适用于仅要求给出最终解的答案,对于需要输出过程的题目并不适用(因为中间的过程在优化空间的时候已经被覆盖了)。

在这道题中, 我们分别使用两个变量来记录前面得到的结果:

int Fibonacci3(int n){
	int n1 = 1;
	int n2 = 1;
	int sum = 0;
	if (n < 3)
		return 1;
	else
	{
		for (int i = 0; i < n - 2; ++i)
		{
			sum = n1 + n2;
			n1 = n2;
			n2 = sum;
		}
		return tmp;
	}
}

时间复杂度:O(N),空间复杂度:O(1)

你可能感兴趣的:(算法)