【教程】python遞歸三部曲(基於turtle實現可視化)-二、漢諾塔

遞歸三部曲:

〇、介紹遞歸及三原則

一、謝爾賓斯基三角形

二、漢諾塔

三、迷宮探索

1、漢諾塔

本教程爲本人在b站投稿的視頻教程對應的文字版
視頻較詳細,文本較簡潔,大家選擇一個看就好

漢諾塔(Tower of Hanoi):漢諾塔(又稱河內塔)問題是源於印度一個古老傳說的益智玩具。
大梵天創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞着64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。並且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤。

抽象成數學問題如下:
有三根相鄰的柱子,標號爲A,B,C
A柱子上從下到上按金字塔狀疊放着n個大小不同(下面的大於上面)的圓盤
要把所有盤子一個一個移動到柱子C上(每次移動時,同一根柱子上小盤子都要在大的圓盤上面)
點擊觀看移動動畫
n=7

2、思路分析

移動過程

A塔是圓盤出發的塔,稱之爲起點塔
C塔是圓盤最終要達到的塔,稱之爲目標塔
B塔起輔助中轉作用,稱之爲中轉塔
那麼各種層數n下,圓盤移動過程分析如圖
在這裏插入圖片描述

代碼

首先,需要添加方法moveTower來繪製指定層數的謝爾賓斯基三角形

def moveTower(height, fromPole, withPole, toPole):
	"""
	:param height: 漢諾塔高度——層數,設爲N
	:param fromPole: 出發的柱子(起點塔A)
	:param withPole: 經過的柱子(中轉塔B),起輔助作用
	:param toPole: 要到達的柱子(目標塔C)
	"""

然後我們再分析下moveTower方法應該怎麼寫
1、只有一層的話,直接將圓盤1從起點塔A移動到目標塔C
否則的話
2、將圓盤1到n - 1看作一個整體,從起點塔A藉助目標塔C移動到中轉塔B
3、再將圓盤n從起點塔A移動到目標塔C
4、將圓盤1到n - 1看作一個整體,從中轉塔B藉助起點塔A移動到目標塔C
爲了實現上面四步,我們需要添加一個輔助方法
moveDisk(diskIndex,fromPole,toPole) : 移動指定層圓盤diskIndex,從fromPole出發,到達toPole
我們先通過簡單的控制檯輸出實現漢諾塔,代碼如下

def moveDisk(diskIndex, fromPole, toPole):
	print_str = 'Move disk %s form %s to %s' % (diskIndex, fromPole, toPole)
	print(print_str)

def moveTower(height, fromPole, withPole, toPole):
	"""
	:param height: 漢諾塔高度——層數,設爲N
	:param fromPole: 出發的柱子(起點塔A)
	:param withPole: 經過的柱子(中轉塔B),起輔助作用
	:param toPole: 要到達的柱子(目標塔C)
	"""
	if height == 1 :
		moveDisk(1, fromPole, toPole)
	else:
		moveTower(height-1, fromPole, toPole, withPole)
		moveDisk(height, fromPole, toPole)
		moveTower(height-1, withPole, fromPole, toPole)

if __name__ == '__main__':
	moveTower(3, "A", "B", "C")

運行結果如下

Move disk 1 form A to C
Move disk 2 form A to B
Move disk 1 form C to B
Move disk 3 form A to C
Move disk 1 form B to A
Move disk 2 form B to C
Move disk 1 form A to C

然後我們結合代碼回顧一下遞歸三原則
如圖所示:
在這裏插入圖片描述

3、可視化實現

要實現可視化,則
1要繪製兩個形狀:塔,圓盤
2要移動繪製後的形狀
代碼實現思路爲:
先定義塔、圓盤對應的形狀畫筆,在通過移動畫筆實現形狀的移動
最終代碼如下

import turtle
 
# ==============
#  常量設置
# ==============
N=3 # 漢諾塔層數限制
 
BasePL=12   # plate的大小基數,修改這個能夠調整plate的大小
TowerP=5    # Tower的線寬
TowerW=110  # Tower的底座寬度
TowerH=200  # Tower的高度
TowerSpace=260  # Tower的之間的距離,從中心到中心
HORIZON=-100    # Tower的底座高度,用於定位
# 動畫速度,5是比較適中的速度
PMS=1
 
# 優化處理
Isjump=True
 
POLES={
    "1": [],
    "2": [],
    "3": [],
}
PLATES=[] # 存儲所有圓盤對象
 
# 塔的顏色
LineColor="black"
# 多個盤子的顏色
FillColors=[
    "#d25b6a",
    "#d2835b",
    "#e5e234",
    "#83d05d",
    "#2862d2",
    "#35b1c0",
    "#5835c0"
]
# 建立窗體
SCR=turtle.Screen()
# SCR.tracer()
SCR.setup(800,600) #設置窗體大小
 
 
# 設置圓盤形狀
def set_plate(pi=0):
    _pi=pi+2
    t = turtle.Turtle()
    t.hideturtle()
    t.speed(0)
    t.penup()
    t.begin_poly()
    t.left(90)
    t.forward(BasePL*_pi)
    t.circle(BasePL, 180)
    t.forward(BasePL * 2 * _pi)
    t.circle(BasePL, 180)
    t.forward(BasePL * _pi)
    t.end_poly()
    p = t.get_poly()
    pname='plate_%s'%pi
    SCR.register_shape(pname, p)
 
 
# 設置塔柱形狀
def set_tower():
    t = turtle.Turtle()
    t.hideturtle()
    t.speed(0)
    t.penup()
 
    t.begin_poly()
    t.left(90)
    t.forward(TowerW)
    t.circle(-TowerP, 180)
    t.forward(TowerW)
    t.forward(TowerW)
    t.circle(-TowerP, 180)
    t.forward(TowerW-TowerP/2)
 
    t.left(90)
    t.forward(TowerH)
    t.circle(-TowerP, 180)
    t.forward(TowerH)
    t.end_poly()
    p = t.get_poly()
    SCR.register_shape('tower', p)
 
 
# 繪製塔柱
def draw_towers():
    set_tower()
    for tx in [-TowerSpace,0,TowerSpace]:
        t3 = turtle.Turtle('tower')
        t3.penup()
        t3.goto(tx,HORIZON)
 
 
# 繪製圓盤
def draw_plates(pn=4):
    plates=[]
    for i in range(pn):
        set_plate(i)
        _plate='plate_%s'%i
        _p=turtle.Turtle(_plate)
        _colorIdx = i % len(FillColors)
        _color=FillColors[_colorIdx]
 
        _p.color(_color,_color)
        _p.speed(PMS)
        plates.append(_p)
    # 反序,大的在前,小的在後
    global PLATES
    PLATES = plates[:]
 
# 繪製移動過程
def draw_move(diskIndex, fromPindex, toPindex):
    p=PLATES[diskIndex-1]
    index_loc={
        "A":1,
        "B":2,
        "C":3
    }
    toP=index_loc.get(toPindex,None)
    fromP=index_loc.get(fromPindex,None)
 
    p.penup()
 
    mx = (toP - 2) * TowerSpace
    my = HORIZON + len(POLES[str(toP)]) * BasePL * 2
 
    if fromP!=None:
        POLES[str(fromP)].remove(p)
        if Isjump:
            px,py=p.pos()
            p.goto(px,TowerH+py)
            p.goto(mx,TowerH+py)
 
    p.goto(mx, my)
    POLES[str(toP)].append(p)
 
 
# 將所有圓盤移動到起點
def movetoA(n,fromPindex):
    for i in range(n,0,-1):
        draw_move(i,None,fromPindex)
 
 
# 移動指定層圓盤diskIndex,從fromPole出發,到達toPole
def moveDisk(diskIndex,fromPole,toPole):
    """
    :param diskIndex: 圓盤的索引(從上往下,第一層爲1,第二層爲2、、、第n層爲n)
    :param fromPole: 出發的柱子(起點)
    :param toPole: 要到達的柱子(終點)
    :return:
    """
    draw_move(diskIndex, fromPole, toPole)
 
 
# 核心函數,入口
def moveTower(height,fromPole, withPole, toPole):
    """
    :param height: 漢諾塔高度——層數
    :param fromPole: 出發的柱子(起點)
    :param withPole: 進過的柱子(中轉點)
    :param toPole: 要到達的柱子(終點)
    :return:
    """
    if height == 1:
        # 基礎情形:一層的漢諾塔
        moveDisk(1,fromPole, toPole)
        return
 
    # 先將圓盤1到n - 1看作一個整體從起點塔移動到中轉塔(用目標塔作爲本步驟的中轉)
    moveTower(height-1,fromPole,toPole,withPole)
    # 再將圓盤n從起點塔A移動到目標塔C
    moveDisk(height,fromPole,toPole)
    # 最後將圓盤1到n - 1看作一個整體從中轉塔移動到目標塔(用起點塔作爲本步驟的中轉)
    moveTower(height-1,withPole,fromPole,toPole)
 
 
if __name__ == '__main__':
    # 調用
    # 三層漢諾塔,A爲出發柱子,B爲中轉柱子,C爲目標柱子
    n=N
     
    SCR.tracer(0)
    draw_towers()
    draw_plates(n)
    movetoA(n,"A")
    SCR.tracer(1)

    moveTower(n,"A","B","C")
    turtle.done()

點擊觀看代碼對應移動動畫

本文主要通過三個實例來幫助大家理解遞歸(其展示動畫已上傳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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章