目录
什么是递归Recursion?
递归是一种解决问题的方法,其精髓在于将问题分解为规模更小的相同问题,持续分解,直到问题规模小到可以用非常简单直接的方式来解决。递归的问题分解方式非常独特,其算法方面的明显特征就是:在算法流程中调用自身。
递归“三定律”
1.必须有一个基本结束条件(最小规模问题的直接解决)
2.必须能改变状态向基本结束条件演进(减小问题规模)
3.必须调用自身(解决减小了规模相同问题)
递归的应用
1.任意进制转换
用最熟悉的十进制分析问题:十进制有十个不同符号:convString="0123456789",比十小的整数,转换成十进制,直接查表就可以了:convString[n];想办法把比十大的整数,拆成一系列比十小的整数,逐个查表,比如七百六十九,拆成七、六、九,查表可以得到769就可以了。
所以,在递归三定律里,找到了“基本结束条件”,就是小于十的整数,拆解整数的过程就是向“基本结束条件”演进的过程。
我们用整数除和求余数两个计算来将整数一步步拆开,除以“进制基base”(//base) ; 对“进制基”求余数(%base)
问题就分解为:余数总小于“进制基base”,是“基本结束条件”,可直接进行查表转换;整数商成为“更小规模”问题,通过递归调用自身解决
def toStr(n,base):
convertString="0123456789ABCDEF"
if n<base:
return convertString[n]#最小规模
else:
return toStr(n//base,base)+convertString[n%base]#减小规模,调用自身
print(toStr(1453,16))
##5AD
2.递归可视化
Python的海龟作图系统turtle module
1.分形树
自相似递归图形。分形是在不同尺度上都具有相似性的事物。
把二叉树分解为三个部分:树干、左边的小树、右边的小树。分解后正好符合递归的定义:对自身的调用。
import turtle
def tree(branch_len):
if branch_len>5:#树干太短不画,即递归结束条件
t.forward(branch_len)#画树干
t.right(20)#右倾斜20度
tree(branch_len-15)#递归调用,画右边的小树,树干减15
t.left(40)#向左回40度,即左倾斜20度
tree(branch_len-15)#递归调用,画左边的小树,树干减15
t.right(20)#右倾斜20度
t.backward(branch_len)#海龟退回原位置
t=turtle.Turtle()
t.left(90)
t.penup()
t.backward(100)
t.pendown()
t.pencolor('green')
t.pensize(2)
tree(75)#画树干长度75的二叉树
t.hideturtle()
turtle.done()
2.谢尔宾斯基三角形
分形构造,平面称为谢尔宾斯基三角形,立体称为谢尔宾斯基金字塔。实际上,真正的谢尔宾斯基三角形是完全不可见的,其面积为0,但周长无穷,是介于一维和二维之间的分数维(约1.585维)构造。根据自相似性,谢尔宾斯基三角形是由3个尺寸减半的谢尔宾斯基三角形按照品字形拼叠而成。
import turtle
def drawTriangle(points,color):#绘制等边三角形
t.fillcolor(color)
t.penup()
t.goto(points['top'])
t.pendown()
t.begin_fill()
t.goto(points['left'])
t.goto(points['right'])
t.goto(points['top'])
t.end_fill()
def getMid(p1,p2):#取两个点的中点
return ((p1[0]+p2[0])/2,(p1[1]+p2[1])/2)
def sierpinski(degree,points):
colormap=['blue','red','green','white','yellow','orange']
drawTriangle(points,colormap[degree])
if degree>0:#最小规模0直接退出
#调用自身,左上右次序
sierpinski(degree-1,
{'left':points['left'],
'top':getMid(points['left'],points['top']),
'right':getMid(points['left'],points['right'])})
sierpinski(degree - 1,
{'left': getMid(points['left'], points['top']),
'top': points['top'],
'right': getMid(points['top'], points['right'])})
sierpinski(degree - 1,
{'left': getMid(points['left'], points['right']),
'top': getMid(points['top'], points['right']),
'right': points['right']})
t=turtle.Turtle()
points={'left':(-200,-100),#外轮廓三个顶点
'top':(0,200),
'right':(200,-100)}
sierpinski(5,points)#画degree为5的三角形
turtle.done()
3.找零兑换(兑换最少个数硬币)
1.贪心策略Greedy Method
因为我们每次都试图解决问题的尽量大的部分对应到兑换硬币问题,就是每次以最多数量的最大面值硬币来迅速减少找零问题。该策略在美元或其他货币的硬币体系下表现尚好,但在有特殊面值的情况下就会变得复杂起来,如下,此时该策略失效。贪心策略是否有效依赖于具体的硬币体系。
硬币[1,5,10,25]: 63=25*2+10*1+1*3
硬币存在21时:63=21*3
2.递归解法
首先,确定基本结束条件:需找零的面值正好等于某种硬币
其次,减小问题的规模。对每种硬币尝试1次,例如美元硬币体系:找零减去1分后,求兑换硬币最少数量(递归调用自身);找零减去5分后,求兑换硬币最少数量;再分别减去10分,25分。上述4项选择最小的一个。
def recMc(coinValueList,change):#硬币体系,找零
minCoins=change#最大能兑换出change个
if change in coinValueList:#最小规模,直接返回1
return 1
else:
for i in [c for c in coinValueList if c<=change]:
#减小规模:每次减去一种硬币面值,挑选最小数量
numCoins=1+recMc(coinValueList,change-i)
if numCoins<minCoins:
minCoins=numCoins
return minCoins
print(recMc([1,5,10,25],63))
#6
递归解法虽然能够解决问题,但其最大的问题是:极其低效,重复计算太多了。
对上面的递归解法进行改进的关键在于消除重复计算:我们可以用一个表将计算过的中间结果保存起来,在计算之前查表看看是否已经计算过。这个算法的中间结果就是部分找零的最优解,在递归调用过程中已经得到的最优解被记录下来。改进后的解法极大地减少了递归调用的次数,运行结果很快得出。
def recDc(coinValueList,change,knownResults):#硬币体系,找零,存储中间结果的表
minCoins=change#最大能兑换出change个
if change in coinValueList:#最小规模,直接返回1
knownResults[change]=1#记录最优解
return 1
elif knownResults[change]>0:
return knownResults[change]#查表成功,直接返回最优解,不进行递归调用
else:
for i in [c for c in coinValueList if c<=change]:
#减小规模:每次减去一种硬币面值,挑选最小数量
numCoins=1+recDc(coinValueList,change-i,knownResults)
if numCoins<minCoins:
minCoins=numCoins
knownResults[change]=minCoins
return minCoins
print(recDc([1,5,10,25],63,[0]*64))
#6
递归调用的实现
当一个函数被调用时,系统会把调用时的现场数据压入到系统调用栈。每次调用,压入栈的现场数据称为栈帧。当函数返回时,要从调用栈的栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回。
Python中的递归深度限制
在调试递归算法程序的时候经常会碰到这样的错误:RecursionError,递归的层数太多,系统调用栈容量有限。这时候要检查程序是否忘记设置基本结束条件,导致无限递归,或者向基本结束条件演进太慢,导致递归层数太多,调用栈溢出。
在Python内置的sys模块可以获取和调整最大递归深度
import sys
print(sys.getrecursionlimit())
sys.setrecursionlimit(3000)
print(sys.getrecursionlimit())