动态规划
从典例开始了解动态规划的前世今生
问: 有4种面值为1,5,10,25的货币,可以重复使用,需要凑成1000元,问共有多少种货币组成方案?
分析问题:
- 当使用0张面值为5元的货币,剩下由{1,10,25}组成1000元的方案总数计为 res1
- 当使用1张面值为5元的货币,剩下由{1,10,25}组成的995元的方案总数计为 res2
- …
- 当使用199张面值为5元的货币,剩下由{1,10,25}组成的5元的方案总数计为 res200
- 当使用200张面值为5元的货币,剩下由{1,10,25}组成的0元的方案总数计为 res201
上述求方案总数的方法都会用到一个递归函数。
那么这里总的方案数为上述结果的累加,即: res1 + res2 + … + res201
这里就可以定义这个递归函数: int(int[] arr, index , aim);
这个递归函数表示的含义是如果用arr[index … N-1] 的面值货币组成的 aim 目标钱数的方案总数
于是就可以得到以下代码:
这种方法又叫暴力搜索算法。但是有个重复计算的问题,比如说在已经使用0张5元和1张10元的情况下,会求volience(source,2,990)的值。当使用2张5元和0张10元的情况下,也会求一次violence(source,2,990)的值。这就造成了重复计算的问题,这种问题在这个题目中出现的次数非常之多,所以我们需要将这些计算过结果的情况保存下来,所以出现了下面将要介绍的记忆搜索算法。
这里的记忆搜索算法的记忆是怎么实现的呢?需要怎样记忆?记忆什么内容?这些问题将是我们需要进一步探讨的。
从上面的分析可知,因为source参数是不变的,只有index和aim值是变化的。所以我们需要记忆的就是violence(index, aim)的值。有两个可变参数,所以我们用一个二维数组的map来记忆这个值。记为 int[][] map = new int[i][j];
i 为index 的大小 +1 ,j 为 aim的大小 + 1。 map[i][j]的值就表示当index = i, aim = j的情况时的方案总数。代码实现:
上面的这个记忆搜索算法就相当于动态规划的一种形态了。下面讲讲两者的区别和联系:
- 记忆搜索不关心达到某一个递归过程的路径,只是单纯的对计算过的递归过程进行记录,避免重复递归的过程。
- 动态规划严格规定了到达目标的路径,只能依次计算得到结果。后面的结果严格依赖前面的计算结果。
- 两者都是用空间换时间的做法,也都有枚举的过程,一个有顺序,一个没顺序。
就多了一个有顺序的枚举过程,为什么一定要去学动态规划呢?那正是因为有顺序的枚举,才有了可以优化算法的空间。
具体的动态规划过程参考动态规划(1)