遞歸三部曲:
〇、介紹遞歸及三原則
一、謝爾賓斯基三角形
二、漢諾塔
三、迷宮探索
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