一、递归
百度百科:程序调用自身的编程技巧称为递归( recursion)。
通用:如果一个函数在内部调用自身本身,这个函数就是递归函数。
借用知乎上Memoria的回答:
假设你在一个电影院,你想知道自己坐在哪一排,但是前面人很多,你懒得去数了,于是你问前一排的人「你坐在哪一排?」,这样前面的人 (代号 A) 回答你以后,你就知道自己在哪一排了——只要把 A 的答案加一,就是自己所在的排了。不料 A 比你还懒,他也不想数,于是他也问他前面的人 B「你坐在哪一排?」,这样 A 可以用和你一模一样的步骤知道自己所在的排。然后 B 也如法炮制。直到他们这一串人问到了最前面的一排,第一排的人告诉问问题的人「我在第一排」。最后大家就都知道自己在哪一排了。
递归问题分析的核心
一个合法的递归定义包含两个部分:基础情况和递归部分。
分析一个递归问题就是列出递归定义表达式的过程。
上面那个电影院排数的问题表达式可以列为:
一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。
一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
注意:
(1) 递归就是在过程或函数里调用自身;
(2) 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。
递归算法一般用于解决三类问题:
(1)数据的定义是按递归定义的。(Fibonacci函数)
(2)问题解法按递归算法实现。(回溯)
(3)数据的结构形式是按递归定义的。(树的遍历,图的搜索)
递归的缺点:
递归算法解题的运行效率较低。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。
python实现:
#递归的实现:
def age(n):
if n == 1:
return 18
return age(n-1)+2
print(age(5))
# age(5)=age(4)+2 第一次进入
# age(4)=age(3)+2 第二次进入
# age(3)=age(2)+2 第三次进入
# age(2)=age(1)+2 第四次进入
# age(1)=18 第五次进入,最后判断终止条件
# age(n)=age(n-1)+2 #n>1 递归终止条件
# age(1)=18 #n=1 等于终止条件
二、动态规划
动态规划,Dynamic Programming(此处“Programming”为“规划”,而非指“程序”、“编程”),研究多步决策过程最优化问题的一种数学方法,英文缩写DP。在动态规划中,为了寻找一个问题的最优解(即最优决策过程),将整个问题划分成若干个相应的阶段,并在每个阶段都根据先前所作出的决策作出当前阶段最优决策,进而得出整个问题的最优解。
可解决问题:
最长公共子序列,最长公共子串,最长递增子序列,最长回文子串,硬币的组合数,硬币的最少组合方法,最小编辑距离,背包问题等。
能采用动态规划求解的问题的一般要具有3个性质:
最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)
解题步骤:
1. 拆分问题
2. 定义状态(并找出初状态)
3. 状态转移方程
例题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就是我们要的最优解。整个算法就是这样一步一步计算过来的。
部分内容参考:
https://blog.csdn.net/qq_38454977/article/details/71246399
https://blog.csdn.net/littlethunder/article/details/26575417