动态规划
动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。
我们常常听到的动态规划思想就是:记忆,空间换时间, 不重复求解, 由交叠子问题从较小问题解逐步决策, 构成较大问题的解。
斐波那契数列可以作为最简单的一个例子解释动态规划思想
计算斐波那契数列(Fibonacci polynomial)的一个最基础的算法是,直接按照定义计算:
1 function fib(n) 2 if n = 0 or n = 1 3 return 1 4 return fib(n − 1) + fib(n − 2)
当n=5时,fib(5)的计算过程如下:
fib(5)
fib(4) + fib(3)
(fib(3) + fib(2)) + (fib(2) + fib(1))
((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
(((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
由上面可以看出,这种算法对于相似的子问题进行了重复的计算,因此不是一种高效的算法。实际上,该算法的运算时间是指数级增长的。 改进的方法是,我们可以通过保存已经算出的子问题的解来避免重复计算:
1 int arr[n] = {0}; 2 3 int fib (int i) 4 { 5 if(i == 1) 6 { 7 arr[i] = 1; 8 } 9 else if(i = 2) 10 { 11 arr[i] = 1; 12 } 13 else 14 { 15 arr[i] = arr[i - 1] + arr[i -2]; 16 } 17 return arr[i]; 18 }
将前n个已经算出的前n个数保存在数组map中,这样在后面的计算中可以直接易用前面的结果,从而避免了重复计算。算法的运算时间变为O(n);
动态规划几个经典问题
1. 最长单调子序列
2. 最长公共子序列
3. 背包问题
在后续的更新的文章中, 会陆续提到这几个问题, 等待更新……