递归三部曲:
〇、介绍递归及三原则
一、谢尔宾斯基三角形
二、汉诺塔
三、迷宫探索
1、迷宫介绍
本教程为本人在b站投稿的视频教程对应的文字版
视频较详细,文本较简洁,大家选择一个看就好
迷宫如下图,蓝色点为起点,绿色点为终点
点击查看迷宫探索动画
迷宫文本已上传github(如下图),点击这里查看
本地新建一个text文件夹,把这些txt放在text文件夹里面
这里我们那301.txt为例,文本内容如下:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 0 0 0 2 0 2 0 0 0 0 0 2 0 0 0 0 0 2 0 0 0 2 0 0 0 0 0 0 0 0 0 1
1 0 1 0 1 0 1 0 1 2 1 2 1 0 1 2 1 2 1 0 1 2 1 0 1 2 1 2 1 0 1 2 1
1 1 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 2 0 2 0 0 0 0 0 0 0 2 0 0 0 1
1 1 1 0 1 2 1 2 1 2 1 0 1 2 1 0 1 2 1 0 1 0 1 0 1 0 1 0 1 2 1 0 1
1 1 0 0 S 0 0 0 0 0 0 0 2 0 2 0 0 0 0 0 0 0 2 0 2 0 2 0 0 0 2 0 1
1 1 1 0 1 0 1 2 1 2 1 2 1 0 1 0 1 2 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
1 1 0 0 2 0 0 0 2 0 0 0 0 0 2 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 1
1 1 1 0 1 0 1 2 1 0 1 0 1 0 1 2 1 2 1 0 1 0 1 0 1 0 1 2 1 0 1 2 1
1 1 0 0 2 0 0 0 0 0 2 0 2 0 0 0 2 0 2 0 2 0 2 0 2 0 0 0 2 0 2 0 1
1 1 1 0 1 0 1 0 1 0 1 2 1 2 1 2 1 0 1 2 1 0 1 0 1 0 1 2 1 0 1 0 1
1 1 0 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 2 0 0 0 2 0 0 0 1
1 1 1 0 1 2 1 2 1 0 1 2 1 2 1 0 1 0 1 2 1 2 1 2 1 2 1 2 1 0 1 0 1
1 1 0 0 2 0 0 0 0 0 0 0 0 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 1
1 1 1 2 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 2 1 0 1 0 1 2 1 0 1 2 1
1 1 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 0 0 2 0 0 0 2 0 2 0 1
1 1 1 2 1 0 1 2 1 2 1 2 1 2 1 2 1 0 1 0 1 2 1 0 1 2 1 0 1 0 1 0 1
1 1 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 2 0 2 0 0 0 0 0 2 0 2 0 0 0 1
1 1 1 0 1 2 1 2 1 0 1 2 1 2 1 0 1 2 1 0 1 0 1 0 1 2 1 0 1 2 1 0 1
1 1 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 2 0 0 E 2 0 2 0 0 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
其中1和2都是墙,0是路,
S为起点,E为终点。
注:迷宫是按照算法随机生成的。这是另外一个生成迷宫问题了,本视频直接取其生成的迷宫来用,生成迷宫的算法就不介绍了,在生成迷宫时墙有1,2的区别,在迷宫探索中1和2无区别,本文就不展开说了。
2、思路分析
迷宫的探索过程为:
从起点出发,分别按顺序往上下左右四个方向去探索(即移动到上下左右的相邻单元格),
在这一过程中递归地讲对探索后的相邻单元格进行进一步四周的探索(即将该相邻单元格当做新的起点去执行上一步骤,直至探索完成或失败,才开始下一个方向的探索)
探索的具体过程可以分下面几种情况:
- 找到终点,探索完成,然后告诉上一步这一步探索成功
- 找到墙或者探索过的点(或者超出迷宫的点),探索失败,然后还是告诉上一步这一步探索是失败的
- 向某个方向的探索得出的结论是成功的(源于1),那么探索完成,不在探索,并且告诉上一步探索这一方向是能够探索成功的
- 向某个方向的探索得出的结论是失败的(源于2),那么换一个方向进行探索
- 向所有方向探索都失败了,那么探索失败,并告诉上一步这一方向探索是失败的
结合以上分析写出探索的递归方法searchNext
如下
def searchNext(mazeList, ci, ri):
if mazeList[ri][ci] == "E":
return True
if not (0<=ci<C and 0<=ri<R):
return False
if mazeList[ri][ci] in ['1','2', 'TRIED']:
return False
mazeList[ri][ci] = "TRIED"
direction = [
(1, 0),
(-1,0),
(0, 1),
(0, -1)
]
for d in direction:
dc, dr = d
found = searchNext(mazeList, ci + dc, ri +dr)
if found:
return True
else:
pass
return False
我们再结合上文探索的几种情况分析一下代码的意思
我们再结合上面的分析回顾下递归三原则
1,要有个基础条件,来退出递归
2,递归过程要向1中的基础情况靠拢
3,要不断的调用自身
- 那么上文分析的五种情况中的1、2就是用于退出递归的基础情况
- 上面代码的第19-22行就是不断地向周围去探索,在有限的迷宫里面,就是在向1、2两个基础条件靠拢
- 代码第22行则是递归的调用了方法本身
3、可视化代码实现
步骤2中我们分析并完成了递归方法searchNext
的逻辑代码
最后我们还需要实现可视化
要实现可视化,则要添加如下几个方法
get_maze_info(filename)
: 通过文件名获取迷宫文本信息,返回二维列表
draw_cell(ci, ri)
: 绘制迷宫墙体单元格
draw_dot(ci, ri, color)
: 绘制迷宫的起点和终点
draw_maze(maze_list)
: 绘制迷宫(所以单元格和起点终点)
draw_path(ci,ri, color="blue"):
: 绘制探索的路径
同时在原有方法searchNext
中添加对于探索路径的绘制
最后添加方法start_search(mazeList)
,用于找到起点,从起点开始调用递归方法searchNext
最终代码如下
import turtle
import random
def get_maze_info(filename):
with open(filename, 'r') as f:
fl = f.readlines()
maze_list = []
for line in fl:
line = line.strip()
line_list = line.split(" ")
maze_list.append(line_list)
return maze_list
txt_path = "text/301.txt"
mazeList = get_maze_info(txt_path)
# print(mazeList)
R, C = len(mazeList), len(mazeList[0])
cellsize = 20
dot_size = 15
dot_t = turtle.Turtle()
line_t = turtle.Turtle()
line_t.pensize(5)
line_t.speed(0)
scr = turtle.Screen()
scr.setup(width=C*cellsize, height=R*cellsize)
scr.colormode(255)
t=turtle.Turtle()
t.speed(0)
def draw_cell(ci, ri):
tx = ci*cellsize - C*cellsize/2
ty = R*cellsize/2 - ri*cellsize
t.penup()
t.goto(tx, ty)
v = random.randint(100, 150)
t.color(v, v, v)
t.pendown()
t.begin_fill()
for i in range(4):
t.fd(cellsize)
t.right(90)
t.end_fill()
def draw_dot(ci, ri, color):
tx = ci*cellsize - C*cellsize/2
ty = R*cellsize/2 - ri*cellsize
cx = tx+cellsize/2
cy = ty-cellsize/2
dot_t.penup()
dot_t.goto(cx,cy)
dot_t.dot(dot_size,color)
def draw_maze(maze_list):
scr.tracer(0)
for ri in range(R):
for ci in range(C):
item = maze_list[ri][ci]
if item in ['1', '2']:
draw_cell(ci, ri)
elif item == "S":
draw_dot(ci,ri, "blue")
elif item == "E":
draw_dot(ci, ri, "green")
def draw_path(ci,ri, color="blue"):
tx = ci*cellsize - C*cellsize/2
ty = R*cellsize/2 - ri*cellsize
cx = tx+cellsize/2
cy = ty-cellsize/2
line_t.color(color)
line_t.goto(cx, cy)
def searchNext(mazeList, ci, ri):
if mazeList[ri][ci] == "E":
draw_path(ci, ri)
return True
if not (0<=ci<C and 0<=ri<R):
return False
if mazeList[ri][ci] in ['1','2', 'TRIED']:
return False
mazeList[ri][ci] = "TRIED"
draw_path(ci, ri)
direction = [
(1, 0),
(-1,0),
(0, 1),
(0, -1)
]
for d in direction:
dc, dr = d
found = searchNext(mazeList, ci + dc, ri +dr)
if found:
draw_path(ci, ri, "green")
return True
else:
draw_path(ci, ri, "red")
return False
def start_search(mazeList):
start_r, start_c = 0, 0
for ri in range(R):
for ci in range(C):
item = mazeList[ri][ci]
if item == "S":
start_r, start_c = ri, ci
line_t.penup()
draw_path(start_c, start_r)
line_t.pendown()
searchNext(mazeList,start_c, start_r)
draw_maze(mazeList)
scr.tracer(1)
start_search(mazeList)
turtle.done()
点击观看代码运行效果
补充说明:
- 为了美观,让迷宫的墙体颜色不是单纯的黑色,而是更有层次更有韵律的富有变化的各种灰色,所以导入了
random
随机库,并在draw_cell
方法里对每一个墙体单元格,随机一个一定范围内的灰色RGB。 - 其中
scr.tracer()
用于开启/关闭turtle绘制动画过程。
scr.tracer(0)
关闭动画,并直接展示绘制后的结果。
scr.tracer(1)
开启动画,展示绘制过程。
当然scr.tracer()
方法本身的意思,并不是开启/关闭turtle绘制动画过程,官方文档解释如下图
其中当n=0时,这个方法则会禁用turtle动画
本文主要通过三个实例来帮助大家理解递归(其展示动画已上传B站):
谢尔宾斯基三角形(Sierpinski Triangle)
汉诺塔(Tower of Hanoi)
迷宫探索(Maze Exploring)
本文代码已上传到github:https://github.com/BigShuang/recursion-with-turtle
本文参考文献:
Problem Solving with Algorithms and Data Structures using Python
turtle官方文档:
中文:https://docs.python.org/zh-cn/3.6/library/turtle.html
英文:https://docs.python.org/3.6/library/turtle.html