1、什么是递归
1.1 概念
所以说递归就是自身调用自身的过程。
为了更好的理解递归,我们用一个例子说明:
问题:求 1+3+5+7+…+n-1+n 的和。我们首先想到的就是for循环,但是现在如果不用循环,这个问题又怎么解决呢?
下面来看使用递归解决这个问题的思路:
下面看看上面程序到底是怎么实现的,注意看下面左图是深入调用的过程,下面右图是返回过程。
所以这个就是一次一次的往下调用,到达返回条件时,再一步一步的返回。
1.2 递归三定律
总结:一个问题能使用递归,其实就是要满足三个条件:(1)问题要有一个结束的条件;(2)问题能分解成更小规模问题;(3)小规模问题和原问题相同,所以才能自身调用自身。
这里要注意,对于前两个条件都是很好理解的,但是对于这第三个条件时很难理解的。具体来说,像前面这个数列求和问题,我们可以一步一步的从调用和返回这两个过程思考,但是一旦问题变得复杂了,要想一步一步弄清楚怎么样的是很难的,所以我们应该从高层次来理解这个问题,只需要理解成规模更小的相同问题即可。
2、递归的应用
2.1 整数转换为任意进制
2.1.1 问题分析:
2.1.2 实现代码:
3、递归调用时,系统是如何实现的?
3.1 原理
3.2 python中递归深度的限制
遇到这样的错误,一般是下面的描述导致无限递归:
一般python默认最大的递归深度是1000,我们可以使用下面的方法改变:
>>>import sys
>>>sys.getrecursionlimit()
1000
>>>sys.getrecursionlimit(3000)
>>>>sys.getrecursionlimit()
3000
4、递归可视化
使用python内置的作图系统turtle module
,所以先了解这个模块是怎么样的。
4.1 python的海龟作图系统turtle module
介绍
使用官网文档来了解更多,这里我挑用到的一些介绍。
例子1:
请想象绘图区有一只机器海龟,起始位置在 x-y 平面的 (0, 0) 点。先执行import turtle
,再执行turtle.forward(100)
,它将(在屏幕上)朝所面对的 x 轴正方向前进 100 像素,随着它的移动画出一条线段。再执行turtle.right(25)
,它将原地右转 25 度,再执行turtle.forward(100)
,效果如下:
>>>import turtle
>>>turtle.forward(100) # 面对的 x 轴正方向前进 100 像素
>>>turtle.right(25) # 原地右转 25 度
>>>>turtle.forward(100) # 在此方向前进100像素
效果:
例子2:我们再看一个绘制正方形的例子:
import turtle
for i in range(4):
turtle.forward(100)
turtle.right(90)
turtle.done() # 表示作图结束
效果:
例子3:绘制五角星:
import turtle
t = turtle.Turtle() # 实例化对象,前面是直接用类调用,一样
t.pencolor('red') # 笔的颜色
t.pensize(3) # 笔的粗细
for i in range(5):
t.forward(100)
t.right(144)
t.hideturtle() # 隐藏海龟,就是把箭头隐藏了
turtle.done()
效果:
4.2 使用turtle模块来画螺旋线(用递归)
import turtle
def draw(t, step):
if step <=0: # 1、边界条件,若小于0则返回
return
# 2、当前问题的任务是:向前走step步,然后右转90度
t.forward(step)
t.right(90)
# 3、任务完成,减小规模(调用自身)
draw(t, step-5)
if __name__ == "__main__":
t = turtle.Turtle() # 实例化对象,前面是直接用类调用,一样
t.pencolor('red') # 笔的颜色
t.pensize(3) # 笔的粗细
draw(t, 100)
turtle.done()
效果:
4.3 用递归方法画:分形树
二叉树就是分形树的一种,所以接下来用递归画一个二叉树。
思路:主要看递归函数,不要想复杂,只需要这么想:完成这么一件事需要三步,第一步是画树干;第二步是画右枝;第三步是画左枝。
import turtle
def tree(branch_len):
if branch_len<=5:
return
t.forward(branch_len)
t.right(20)
tree(branch_len - 15)
t.left(40)
tree(branch_len - 15)
t.right(20)
t.backward(branch_len)
if __name__ == "__main__":
t = turtle.Turtle() # 实例化对象,前面是直接用类调用,一样
t.pencolor('blue') # 笔的颜色
t.pensize(3) # 笔的粗细
t.left(90)
t.penup() # 起笔,不画
t.backward(100)
t.pendown() # 结束,后面调用的forward就画了
branch_len = 75
tree(branch_len)
turtle.done()
效果:
4.4 用递归方法画:谢尔并宾斯基三角形
(1)概念:
就是下面这样:
(2)作图思路:
(3)实现代码:
import turtle
def sierpinski(degree, points):
colormap = ['blue', 'red', 'green', 'white', 'yellow', 'orange']
drawTriangle(points, colormap[degree]) # 画出外轮廓
if degree > 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']})
def drawTriangle(points, color): # 绘制等边三角形
t.fillcolor(color)
t.penup() # 起笔不画
t.goto(points['top']) # goto函数表示:海龟移动到一个绝对坐标。
t.pendown() # 结束
t.begin_fill() # 在绘制要填充的形状之前调用。
t.goto(points['left'])
t.goto(points['right'])
t.goto(points['top'])
t.end_fill() #填充上次调用 begin_fill() 之后绘制的形状。
def getMid(p1, p2): # 去两个点的中间点
return ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2)
if __name__ == "__main__":
t = turtle.Turtle()
points = {'left':(-200, -100), # 外轮廓三个顶点
'top':(0, 200),
'right':(200, -100)}
sierpinski(5, points)
turtle.done()
结果:
为了更好的理解代码,下面的图是绘制的过程。其实递归不要想复杂,只需要这么想:
先将当前的三角形画好,然后递归调用画左、上、右,这便是完成了一次绘制过程,至于后面每一个里面的绘制都和这个绘制过程一样,所以无需再想。
5 复杂递归问题
在之前的章节中我们探究了一些相对容易和灵活有趣的问题来帮助大家理解递归。在这个部分我们将考虑一些用迭代法解决很困难但用递归却很简单的问题。最后我们会以一个看似能用简洁的递归解决但实际却不能的问题来作为结尾。
5.1 递归应用:汉诺塔
(1) 概念:
(2) 递归实现思想:
这个思想其实是这样的:
要知道,所有的盘片都在1号柱子上,2号柱子是中间柱子,3号柱子是目标柱子。
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=分隔符-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=分隔符-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
(1)要想到递归的三个要素:结束条件,如何减小规模,调用自身;
(2)将这n个盘片想成是第n个和前n-1个这两组;
(3)想办法将这第n个盘子放在3号柱子,那不就将问题的规模减小了一个么,所以当n小于1那不就是结束条件嘛,那第n个盘子是怎么放在3号柱子的呢?首先将上面的n-1个盘子放到2号中间柱子,然后将第n个盘子放到3号柱子;
(4)最后一步是将2号柱子的n-1个盘子放到3号柱子(怎么放的?还是和前面一样,分成第n-1和剩下的,所以这一步不用再想了,直接调用自身即可,不然越想越乱)。
(3) 实现代码:
def moveTower(height, fromPole, withPole, toPole):
if height >=1:
# 将前n-1个盘子从开始的柱子1经过目标柱3移动到2号柱子
moveTower(height-1, fromPole, toPole, withPole)
# 将原来在最底层的最大圆盘移动到目标杆
print(f"Moving disk[{height}] from {fromPole} to {toPole}")
# 将放置在中间柱的n-1个盘子经过开始柱子1移动到3号目标柱子
moveTower(height-1, withPole, fromPole, toPole)
if __name__ == "__main__":
moveTower(5, "#1", "#2", "#3")
注意:现在在读过moveTower,可能会好奇为什么没有一个用以精确地追踪哪一个圆盘在哪一个杆上的数据框。(提示:如果要精确地记录圆盘的移动,可以用三个栈分别对应三根杆)。
5.2 递归应用:探索迷宫
(1)概念:
就是如何走出迷宫问题,在具体实现的时候,是用定义的字符来表示通路和墙壁,具体看下面:
(2)算法思路:
(3)作图思路:
(4)实现代码:
下面是最重要的代码:
迷宫文件如下所示:
++++++++++++++++++++++
+ + ++ ++ +
+ ++++++++++
+ + ++ ++++ +++ ++
+ + + + ++ +++ +
+ ++ ++ + +
+++++ + + ++ + +
+++++ +++ + + ++ +
+ + + S+ + +
+++++ + + + + + +
++++++++++++++++++++++
完整代码如下:
import turtle
PART_OF_PATH = 'O'
TRIED = '.'
OBSTACLE = '+'
DEAD_END = '-'
class Maze: #迷宫类
def __init__(self,mazeFileName):
rowsInMaze = 0
columnsInMaze = 0
self.mazelist = []
mazeFile = open(mazeFileName,'r')
rowsInMaze = 0
for line in mazeFile:
rowList = []
col = 0
for ch in line[:-1]:
rowList.append(ch)
if ch == 'S':
self.startRow = rowsInMaze
self.startCol = col
col = col + 1
rowsInMaze = rowsInMaze + 1
self.mazelist.append(rowList)
columnsInMaze = len(rowList)
self.rowsInMaze = rowsInMaze
self.columnsInMaze = columnsInMaze
self.xTranslate = -columnsInMaze/2
self.yTranslate = rowsInMaze/2
self.t = turtle.Turtle()
self.t.shape('turtle')
self.wn = turtle.Screen()
self.wn.setworldcoordinates(-(columnsInMaze-1)/2-.5,-(rowsInMaze-1)/2-.5,(columnsInMaze-1)/2+.5,(rowsInMaze-1)/2+.5)
def drawMaze(self):
self.t.speed(10)
for y in range(self.rowsInMaze):
for x in range(self.columnsInMaze):
if self.mazelist[y][x] == OBSTACLE:
self.drawCenteredBox(x+self.xTranslate,-y+self.yTranslate,'orange')
self.t.color('black')
self.t.fillcolor('blue')
def drawCenteredBox(self,x,y,color):
self.t.up()
self.t.goto(x-.5,y-.5)
self.t.color(color)
self.t.fillcolor(color)
self.t.setheading(90)
self.t.down()
self.t.begin_fill()
for i in range(4):
self.t.forward(1)
self.t.right(90)
self.t.end_fill()
def moveTurtle(self,x,y):
self.t.up()
self.t.setheading(self.t.towards(x+self.xTranslate,-y+self.yTranslate))
self.t.goto(x+self.xTranslate,-y+self.yTranslate)
def dropBreadcrumb(self,color):
self.t.dot(10,color)
def updatePosition(self,row,col,val=None):
if val:
self.mazelist[row][col] = val
self.moveTurtle(col,row)
if val == PART_OF_PATH:
color = 'green'
elif val == OBSTACLE:
color = 'red'
elif val == TRIED:
color = 'black'
elif val == DEAD_END:
color = 'red'
else:
color = None
if color:
self.dropBreadcrumb(color)
def isExit(self,row,col):
return (row == 0 or
row == self.rowsInMaze-1 or
col == 0 or
col == self.columnsInMaze-1 )
def __getitem__(self,idx):
return self.mazelist[idx]
def searchFrom(maze, startRow, startColumn): # 递归函数
# try each of four directions from this point until we find a way out.
# base Case return values:
# 1. We have run into an obstacle, return false
maze.updatePosition(startRow, startColumn)
if maze[startRow][startColumn] == OBSTACLE :
return False
# 2. We have found a square that has already been explored
if maze[startRow][startColumn] == TRIED or maze[startRow][startColumn] == DEAD_END:
return False
# 3. We have found an outside edge not occupied by an obstacle
if maze.isExit(startRow,startColumn):
maze.updatePosition(startRow, startColumn, PART_OF_PATH)
return True
maze.updatePosition(startRow, startColumn, TRIED)
# Otherwise, use logical short circuiting to try each direction
# in turn (if needed)
found = searchFrom(maze, startRow-1, startColumn) or \
searchFrom(maze, startRow+1, startColumn) or \
searchFrom(maze, startRow, startColumn-1) or \
searchFrom(maze, startRow, startColumn+1)
if found:
maze.updatePosition(startRow, startColumn, PART_OF_PATH)
else:
maze.updatePosition(startRow, startColumn, DEAD_END)
return found
myMaze = Maze(r'C:\Users\MSZ\Desktop\PythonDSExamplePrograms\PythonDSExamplePrograms\Chapter4\maze2.txt')
myMaze.drawMaze()
myMaze.updatePosition(myMaze.startRow,myMaze.startCol)
searchFrom(myMaze, myMaze.startRow, myMaze.startCol)
运行结果: