动态规划的本质是递推问题
课本定义
动态规划即动态地解决问题,从已经解决过的小问题中得到大问题的解,最终得到目标问题的解。动态规划解决的问题具有两个基本特征:
- 重叠子问题
- 最优子结构
重叠子问题
所谓的重叠,即重复,子问题存在大量重复。这有两个关键点:
- 原问题可以分解成为一系列的子问题。
- 这些子问题存在者大量的重复。
最优子结构
最终问题的最优解可以从子问题的最优解中获得。
动态规划问题本质上是递推问题
动态规划形式上定义可能看起来复杂或者高级,但其本质就是根据数列递推公式求值的问题,如果能够根据递推公式进一步求出通项公式,那么就能够得到一个O(1)的算法。所以,只要是动态规划问题就一定是递推问题。
实际上面临的问题,能写出递推公式就比较困难了,对于通项公式,通常很难求得,甚至是不可能的。计算机是适合做递推的,但是如果发生指数爆炸或者运算复杂度的幂次很高,那么随着问题规模的增大,计算机也会变得很吃力。
递推问题的三种解法
- (反向递推)递归法。按照递推公式直接递归地求解。
- (反向递推)备忘法。如果递归中存在潜在的重复计算,那么将每次计算的结果存起来,以后需要时不再求解,直接使用。
- (正向递推)制表法。重复计算的根本原因是从上向下求解,大问题可能依赖了很多前项问题,而对这些前项问题的求解,可能存在重复的依赖。
/*===========================
* f(n) = 2*f(n-1) + 3*f(n-2), f(0)=1, f(1)=2
*===========================*/
#define N 100
/** 递归(反向递推)法 **/
int f1(int n)
{
if(n == 0)return 1;
if(n == 1)return 2;
return 2*f1(n-1) + 3*f1(n-2);
}
/** 反向递推备忘录法 **/
int memo[10];
int f2(int n)
{
if(n == 0)return 1;
if(n == 1)return 2;
if(memo[n-1]==0) memo[n-1] = f2(n-1);
if(memo[n-2]==0) memo[n-2] = f2(n-2);
return 2*memo[n-1] + 3*memo[n-1];
}
/** 正向递推法 **/
int f3(int n)
{
int fn2 = 1, fn1 = 2;
for(int i=2;i<=10;i++)
{
int f = 2*fn1 + 3*fn2;
fn2 = fn1;fn1 = f;
}
return fn1;
}
int main()
{
printf("f1(%d)=%d\n,N,f1(N));
printf("f2(%d)=%d\n,N,f2(N));
printf("f3(%d)=%d\n,N,f2(N));
return 0;
}
递推问题的数学模型
g
n
=
f
(
g
0
,
g
1
,
g
2
,
…
,
g
n
−
1
)
,
f
是不确定的。
g_n = f( g_0,g_1,g_2,…,g_{n-1} ),f是不确定的。
gn=f(g0,g1,g2,…,gn−1),f是不确定的。
对于简单问题,
f
f
f可以很简单,可以是线性的,也可以是非线性的,总之,从数学的函数映射理解就能包含所有的情况。
动态规划问题的解题方法
既然动态规划问题是递推问题,那么递推问题的解题方法就是动态规划问题的解题方法。实际上,只不过动态规划问题相对于一般的递推问题,存在一些特殊性:
- f f f通常与 m a x , m i n , m a x m i n , m i n m a x , m a x m a x , m i n m i n max, min, max min, min max, max max, min min max,min,maxmin,minmax,maxmax,minmin等待最值函数相关;
- 通项可能涉及多项前项,因此存在大量重复依赖。
动态规划是算法中比较难的一类,但是要相信,只要掌握了递推,就能够掌握动态规划。动态规划本身并不是一种独立的解题方法,而是一种问题模式,动态规划的最终解题方法必定可以通过递推问题的三种方法解决。