什么是递归Recursion?
递归是一种解决问题的方法,其精髓在于
- 将问题分解成规模更小的相同问题
- 持续分解,直到问题规模小到可以用非常简单直接的方式来解决
- 递归的问题分解方式非常独特,其算法方面明显特征就是:在算法流程中调用自身。
例如,
不确定长度的列表求和
#方法1
def listsum(numList):
theSum = 0
for i in numList:
theSum = theSum + i
return theSum
#方法2
def listsum(numList):
if len(numList) == 1:
return numList[0]
else:
return numList[0] + listsum(numList[1:])
print(listsum([1,3,5,7,9]))
递归“三定律”
1.递归算法必须有一个基本结束条件(最小规模问题的直接解决)
列表长度为1时,世界输出所包含的唯一数
2.递归算法必须能改变状态向基本结束条件演进(减小问题规模)
每次提出1个数,向长度为1的状态演进
3.递归算法必须调用自身(解决减小了规模的相同问题)
并不需要搞得那么清楚,就是相同问题的解决
递归的引用:任意进制转换
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))
递归调用的实现
当一个函数被调用的时候,系统会把调用时的现场数据压入系统调用栈
每次调用,压入栈的现场数据称为栈帧
当函数返回时,要从调用栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回。
Python中的递归深度限制
RecursionError:递归的层数太多,系统调用栈容量有限
这时候要检查程序中是否忘记设置基本结束条件,导致无限递归,或者向基本结束条件演进太慢,导致递归层数太多,调用栈移除。
在Python内置的sys模块可以获取和调整最大递归深度
import sys
sys.getrecursionlimit()
1000
sys.setrecursionlimit(3000)
sys.getrecursionlimit()
3000
递归可视化:分形树
一个递归作图的例子:螺旋
import turtle
t = turtle.Turtle()
def drawSpiral(t,lineLen):
if lineLen > 0:
t.forward(lineLen)
t.right(90)
drawSpiral(t,lineLen - 5)
drawSpiral(t,100)
turtle.done()
可以简单的理解为Turtle ()函数创建了一个对象,这个对象就是p,然后你利用了对象p的方法如speed()、pensize()等
为了体会turtle.Turtle(),自我感觉是决定了乌龟的起点,如果在函数里面将多次调用使得乌龟每次都落回起点上
所以这样写在函数前就可以了
分形树:自相似递归图形
自相似性质:一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分(至少近似地)是整体缩小后的形状
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)#画树干长度72的二叉树
t.hideturtle()
turtle.done()
递归可视化:谢尔宾斯基三角形
谢尔宾斯基三角形是由3个尺寸减半的谢尔宾斯基三角形按照品字形拼叠而成
我们无法真正做出谢尔宾斯基三角形(degree->无穷),只能做degree有限的近似图形
import turtle
def sierpinski(degree,points):
colormap = ['blue','red','green','white','yellow','orange']
drawTriangle(points,colormap[degree])#等边三角形
if degree > 0:#最小规模,0直接退出
#减小规模:getMid边长减半,调用自身左上右次序
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']})
#绘制deng'bian'san'j等边三角形
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])a / 2,(p1[1] + p2[1]) /2 )
t = turtle.Turtle()
#外轮廓三个顶点
points = {'left':(-200,-100),
'top':(0,200),
'right':(200,-100)}
sierpinski(5,points)#画degree = 5的三角形
turtle.done()
递归的应用:汉诺塔
分解为递归形式:
假设我们有5个盘子,穿在1#柱,需要挪到3#柱
如果能有办法把最上面的一摞4个盘子统统挪到2#
把剩下的最大号盘子直接从1#柱挪到2#柱
再把2#柱上的一摞4个盘子挪到3#柱,就完成了整个移动
接下来的问题就是解决4个盘子如何能从1#挪到2#?
同样是想办法把上面的一摞3个盘子挪到3#柱,
把剩下最大号盘子从1#挪到2#柱
再把一摞3个盘子从3#挪到2#柱
一摞3个盘子的挪动也照此:
分为上面一摞2个,和下面最大号盘子
递归思路
将盘片塔从开始柱,经由中间柱,移动到目标柱:
- 首先将上层N-1个盘片的盘片塔,柱从开始柱,经由目标,移动到中间柱;
- 然后将第N个(最大的)盘片,从开始柱,移动到目标柱;
- 最后将放置在中间柱的N-1个盘片的盘片塔,经由开始柱,移动到目标柱
基本结束条件,也就是最小规模问题是:1个盘片的移动问题
def moveTower(height,fromPole,withPole,toPole):
if height >= 1:
moveTower(height - 1,fromPole,toPole,withPole)
moveDisk(height,fromPole,toPole)
moveTower(height - 1,withPole,fromPole,toPole)
def moveDisk(disk,fromPole,toPole):
print(f"Moving disk[{disk}] from {fromPole} to {toPole}")
moveTower(3,'#1','#2','#3')
值得学习的:函数命名可以具体一点,print参数
递归的应用:探索迷宫
将海龟放在迷宫中间,如何能找到出口?
首先,我们将整个迷宫的空间(矩形)分为行列整齐的方格,区分出墙壁和通道
考虑用矩阵方式来实现迷宫数据结构,采用“数据项为字符列表的列表”这种两级列表的方式来保存方格内容
详解请看大爽歌博客
点击观看配套B站视频(记得点赞投币哟)
STEP1:准备工作
STEP2:提取txt迷宫信息到列表里来
STEP3:根据迷宫信息绘制迷宫
分步1:定义画墙函数
坐标计算如图
分步2:定义画点函数
分布3:画迷宫
STEP4:核心——递归方法探索
分步1:定义画路径函数
分步2:建立探索规则
分步3:下一步(该点四个方向)调用自身
STEP5:找到起点初始化
STEP6:调用函数