程序调用自身的编程技巧称为递归。
它将一个规模较大的问题转化为计算一个相似的且规模较小的问题来求解。
递归能用很简洁的语法优雅地描述出一个问题的特征并且解决它。
但是也存在着不少问题
1、速度问题。函数的调用时需要时间的,反复的调用会使得算法的速度下降
2、重复计算。递归中非常容易出现重复计算一个值的问题。
3、爆栈。如果说一个问题的规模很大,那就无法避免得要将很多个结果压进栈中,超出了栈的处理能力,就会出错,俗称爆栈。
就像递归求解斐波那契数列
def fib(n):
if n == 1 or n == 2:
return 1
else:
return fib(n-1) + fib(n-2)
假设要求第x个斐波那契树(x这个值非常大),那么程序非常容易爆栈。由于多次计算了相同的值和多次调用了函数,时间会下降得非常厉害。
动态规划是运筹学的一个分支,是求解决策过程最优化的数学方法。
动态规划算法是 '记住求过的解来节省时间'
动态规划是一种典型的算法,它可以通过将一个给定的复杂问题分割为子问题,然后将子问题的结果进行存储以免再次计算相同的结果。当问题具有下面两个属性(特征)的时候,那这个问题可以用动态规划的思想来解决。
1.重叠子问题
如果说这个问题可以分解成有限个子问题,并且存在大量重复,那就说明这个问题有重叠子问题的性质。
如果说采用递归求解斐波那契数列:
计算过程大致如下
细心观察发现,多个数值被重复计算了多次,比如fib(3)
为了避免这种不必要的动作通常可以采用
a) 记忆法 (自顶向下):
它在计算之前要查看一个查询表,如果该值已经存在于表中,则直接带入计算,不然才计算,并且将计算结果放入表中。
dic = {}
def fib(n):
global dic
if n not in dic:
if n == 1 or n == 2:
dic[n] = 1
else:
dic[n] = fib(n-1) + fib(n-2)
return dic[n]
b) 制表法 (自底向上):
自底向上也是差不多用表记录
def fib(n):
lis = []
lis.append(1)
lis.append(1)
for i in range(2,n):
lis.append(lis[i-1]+lis[i-2])
return lis[n-1]
当然,也可以只记录两个数值
def fib(n):
if n == 1 or n==2 :return 1
f1 = 1
f2 = 1
for i in range(n-2):
f1,f2 = f1+f2,f1
return f1
2、最优子结构
假设当前决策结果是f[n],则最优子结构就是要让f[n-k]最优,最优子结构性质就是能让转移到n的状态是最优的,并且与后面的决策没有关系,即让后面的决策安心地使用前面的局部最优解的一种性质。