1 任务
递归及DP:学习递归思想,动态规划思想,并同时温习前四天内容,做出总结!
2 概念介绍
-
递归:在调用一个函数的过程中,直接或间接地调用了函数本身这个就叫递归。简而言之就是自己调用自己。大概步骤如下:
1、写出临界条件
2、找出这一次和上一次关系
3、假设当前函数已经能用,调用自身计算上一次的结果,再求出本次的结果
递归两个基本要素:
(1) 边界条件:确定递归到何时终止,也称为递归出口。
(2) 递归模式:大问题是如何分解为小问题的,也称为递归体。
一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
递归算法一般用于解决三类问题:
(1)数据的定义是按递归定义的。(Fibonacci函数)
(2)问题解法按递归算法实现。(回溯)
(3)数据的结构形式是按递归定义的。(树的遍历,图的搜索)
递归的缺点: 递归算法解题的运行效率较低。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。 -
动态规划(DP):Dynamic Programming(这里的“Programming”为“规划”,而非指“程序”、“编程”),研究多步决策过程最优化问题的一种数学方法,英文缩写DP。在动态规划中,为了寻找一个问题的最优解(即最优决策过程),将整个问题划分成若干个相应的阶段,并在每个阶段都根据先前所作出的决策作出当前阶段最优决策,进而得出整个问题的最优解。
当求解的问题满足以下两个条件时, 就应该使用动态规划:
1.主问题的答案 包含了 可分解的子问题答案 (也就是说,问题可以被递归的思想求解)
2.递归求解时, 很多子问题的答案会被多次重复利用
可解决问题: 最长公共子序列,最长公共子串,最长递增子序列,最长回文子串,硬币的组合数,硬币的最少组合方法,最小编辑距离,背包问题等。
动态规划的方法主要有两个:
1)直接自顶向下实现递归式,并将中间结果保存,这叫备忘录法;
创建了一个n+1大小的数组来保存求出的斐波拉契数列中的每一个值,在递归的时候如果发现前面fib(n)的值计算出来了就不再计算,如果未计算出来,则计算出来后保存在Memo数组中,下次在调用fib(n)的时候就不会重新递归了。
2)按照递归式自底向上地迭代,将结果保存在某个数据结构中求解。
1.先计算子问题,再由子问题计算父问题。
2.当前问题可以拆分为多个子问题,并且依赖于这些子问题,那么我们称为此问题符合子结构,而若当前状态可以由某个阶段的某个或某些状态直接得到,那么就符合最优子结构。
例题1 背包问题(01背包,完全背包,多重背包)
首先分别解释一下三种背包的含义:
- 01背包:有n种物品与承重为m的背包。每种物品只有一件,每个物品都有对应的重量weight[i]与价值value[i],求解如何装包使得价值最大
- 完全背包:有n种物品与承重为m的背包。每种物品有无限件,每个物品都有对应的重量weight[i]与价值value[i],求解如何装包使得价值最大
- 多重背包:有n种物品与承重为m的背包。每种物品有有限件num[i],每个物品都有对应的重量weight[i]与价值value[i],求解如何装包使得价值最大
思路:遇到这种问题,最简单也是最耗费时间的解决方法是遍历,将所有情况都计算出来,再从结果中找最优解,我们今天介绍的动态规划就是在这个基础上演变而来的。我们都知道,遍历之所以耗费时间,是由于它的计算量巨大,我们通过观察它的计算过程可以发现,其实它的很多计算操作计算的都是之前计算过的量。那我们如果把这些已经计算过的量储存起来,当我们需要的适合直接提取它的结果,就起到了减少计算量的效果,我们的动态规划使用的就是这种思想。
#-*- coding: utf-8 -*-
def max(m,n):
if(m>n):
return m
if(m<n):
return n
def max1(m,n):
if(m<n):
return true
if(m>n):
return false
def best(m,list1,list2,n):
r=[[0 for i in range(n)] for i in range(m+1)];
for i in range(m+1):
for j in range(n):
r[i][j]=0
for i in range(1,m+1):
for j in range(1,n):
r[i][j] = r[i][j-1]
if(i>=list1[j]):
r[i][j]=max(r[i][j-1],r[i-list1[j]][j-1]+list2[j])
return r[m][n-1];
if __name__ =="__main__":
m= 0;
print"请输入最大重量"
m = input()
print"请输入一组物品的总数量"
n=input()
n=n+1
list1 = [0 for i in range(n)]
list2 = [0 for i in range(n)]
print"请依次输入物品的质量"
for i in range(1, n):
list1[i] = int(input())
print"请依次输入物品的价值"
for i in range(1, n):
list2[i] = int(input())
list3[i]=0
q=best(m,list1,list2,n)
print q
这段代码最重要的部分就是best函数,我们来一行一行分析一下。
首先它创建了一个m+1行n列的数组,用于存储我们已经计算过的数据,r[m][n-1]的意思就是当背包最大重量为m、物品为前n个时,能装下的物品总价格最高的值。(要注意到我们在主函数输入n之后将n加1一次,这么做是为了方便计算,把n加1后可以留出来n=0的位置表示没有物品时的最优解的值)
接下来我们把r数组初始化为0,然后,运用动态规划的基本思想,先限制一个背包的最大重量,再挨个的增加物品数量,求它的最大值,为了理解方便,我们通过这个图来解释。
我们现在有五个物品abcde,weight是他们的重量,value是他们的价格,浅蓝色的是我们限制的最大重量。那么这张图是怎么得来的呢?
以当最大重量为6时,a行的12的计算方法为例。这就关系到我们代码中最关键的一行:
r[i][j]=max(r[i][j-1],r[i-list1[j]][j-1]+list2[j])
其中r[i][j]是我们要计算的结果,我们要通过比较r[i][j-1]与r[i-list1[j]][j-1]+list2[j]的值来判断是否把a放进去,此时i=6,j=5(这个图是从下往上生成的,与我们的代码循环方式相反)r[i][j-1]的值可以从表上得到为9,,r[i-list1[j]][j-1]+list2[j]的意思为把a所占的重量出去后bcde在总重量减去a重量的限制中能达到的最优解,减去a后总重量为4,我们可以在b那一行,浅蓝色栏为4的表格中找到此时最优解为6,之后再加上a的价格,就得出了12,再最后与9比较,12大于9,所以12就是我们要的最优解。整个算法就是这样一步一步计算过来的。