中国大学MOOC,数据结构与算法(Python版)学习笔记
课程网址:“https://www.icourse163.org/course/PKU-1206307812”
什么是递归
递归是一种解决问题的方法,其精髓在于将问题分解为规模更小的相同问题,持续分解,直到问题规模小到可以用非常简单直接的方式来解决。
递归的问题分解方式非常独特,其算法方面的明显特征就是:在算法流程中调用自身。递归为我们提供了一种对复杂问题的优雅解决方案,精妙的递归算法常会出奇简单,令人赞叹。
初识递归:数列求和
只是一个很简单的问题:给定一个列表,返回所有数的和。列表中数的个数不定,如果用一个循环和一个累加变量来迭代求和也很简单:
def listsum(numList):
theSum = 0
for i in numList:
theSum = theSum + i
return theSum
print(listsum([1, 3, 2, 4, 6]))
如果不用循环可以怎么求解这个问题?
求和实际上是由一次次的两数相加实现的,所以可以使用递归方式实现数列求和的问题:
数列的和=“首个数”+“余下数列”的和;如果数列包含的数少到只有1个的话,它的和就是这个数了。
def listSum(numList):
if len(numList) == 1:
return numList[0]
else:
return numList[0] + listSum(numList[1:])
print(listSum([1, 3, 2, 4, 6]))
数列求和问题首先具备了基本结束条件:当列表长度为1的时候,直接输出所包含的唯一数。
数列求和处理的数据对象是一个列表,而基本结束条件是长度为1的列表,那递归算法就要改变列表并向长度为1的状态演进,其具体做法是将列表长度减少1。调用自身是递归算法中最难理解的部分,实际上我们理解为“问题分解成了规模更小的相同问题”就可以了,在数列求和算法中就是“更短数列的求和问题”。
递归算法的要点
- 递归算法必须有一个基本结束条件(最小规模问题的直接解决)
- 递归算法必须能改变状态向基本结束条件演进(减小问题规模)
- 递归算法必须调用自身(解决减小了规模的相同问题)
递归算法的应用——进制转换
我们用最熟悉的十进制分析这个问题:十进制有十个不同符号:
c
o
n
v
S
t
r
i
n
g
=
“
0123456789
”
convString = “0123456789”
convString=“0123456789”,比十小的整数,转换成十进制,直接查表就可以了:
c
o
n
v
S
t
r
i
n
g
[
n
]
convString[n]
convString[n];比十大的整数:想办法把比十大的整数,拆成一系列比十小的整数,然后逐个查表,比如七百六十九,拆成七、六、九,查表得到769就可以了。
所以 ,我们已经找到了“基本结束条件”,就是小于十的整数拆解整数的过程就是向“基本结束条件”演进的过程;我们用"整数除" ,和"求余数"两个计算来将整数一步步拆开,除以“进制基base”(// base)对“进制基”求余数(% base)。
问题就分解为:余数总小于“进制基base”,是“基本结束条件”,可直接进行查表转换整数商成为“更小规模”问题,通过递归调用自身解决
def toStr(n, base):
converString = "0123456789ABCDEF"
if n < base:
return converString[n] # 最小规模
else:
return toStr(n//base, base) + converString[n%base] # 减小规模,调用自身
print(toStr(1453, 16))
递归调用的实现
当一个函数被调用的时候,系统会把调用时的现场数据压入到系统调用栈;每次调用,压入栈的现场数据称为栈帧;当函数返回时,要从调用栈的栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回。
Python中的递归深度限制
在调试递归算法程序的时候经常会碰到这样的错误:RecursionError,即递归的层数太多,系统调用栈容量有限。
这时候要检查程序中是否忘记设置基本结束条件,导致无限递归或者向基本结束条件演进太慢,导致递归层数太多,调用栈溢出。
在Python内置的sys模块可以获取和调整最大递归深度: