【数据结构与算法Python版学习笔记】递归(Recursion)——定义及应用:分形树、谢尔宾斯基三角、汉诺塔、迷宫

定义

  • 递归是一种解决问题的方法,它把一个问题分解为越来越小的子问题,直到问题的规模小到可以被很简单直接解决。
  • 通常为了达到分解问题的效果,递归过程中要引入一个调用自身的函数。

举例

  • 数列求和
def listsum(numlist):
    if len(numlist) == 1:
        return numlist[0]
    else:
        return numlist[0]+listsum(numlist[1:])

if __name__ == "__main__":
    print(listsum([1, 3, 5, 7, 9]))

递归三大定律

就像阿西莫夫(l.Asimov) 的“机器人三定律”一样,递归算法也要遵循三条重要的定律

  • 递归算法必须有个基本结束条件
  • 递归算法必须改变自己的状态并向基本结束条件演进
  • 递归算法必须递归地调用自身

应用

整数转换为任意进制

def toStr(n, base):
    convertString = "0123456789ABCDEF"
    if n < base:
        return convertString[n] # 最小规模
    else: 
        return toStr(n//base,base)+convertString[n % base] # 减小规模,调用自身

if __name__ == "__main__":
    print(toStr(10,2))
    print(toStr(255,16))

递归调用的实现

  • 当一个函数被调用的时候,系统会把调用时的现场数据压入到系统调用栈

    • 每次调用,压入栈的现场数据成为栈帧
    • 当函数返回时,要从调用栈的栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回
  • 易碰到错误:RecursionError

    • 递归的层数太多,系统调用栈容量有限
    • 递归深度限制
      • 检查程序中是否忘记设置基本结束条件,导致无限递归
      • 或者向基本结束条件演进太慢,导致递归层数太多,调用栈溢出
    • 在python内置的sys模块可以获取和调整最大递归深度
    import sys
    sys.getrecursionlimit()#默认1000
    sys.setrecursionlimit(3000)#自定义设置
    sys.getrecursionlimit()
    

递归可视化

海归作图 turtle module

  • 爬行
    • forward(n)
    • backward(n)
  • 转向
    • left(a)
    • right(a)
  • 抬笔放笔
    • penup()
    • pendown()
  • 笔属性
    • pensize(s)
    • pencolor(c)

image

# 递归
def listsum(numlist):
    if len(numlist) == 1:
        return numlist[0]
    else:
        return numlist[0]+listsum(numlist[1:])

def toStr(n, base):
    convertString = "0123456789ABCDEF"
    if n < base:
        return convertString[n]
    else: 
        return toStr(n//base,base)+convertString[n % base]

import turtle

def draw_forward():
    """前向示例"""
    t=turtle.Turtle()
    # 作图开始
    t.forward(100) #指挥海归作图
    # 作图结束
    turtle.done()

def draw_square():
    """绘制正方形"""
    t=turtle.Turtle()
    for i in range (4):
        t.forward(100) 
        t.right(90)

    turtle.done()

def draw_pentagram():
    """绘制五角星"""
    t=turtle.Turtle()
    for i in range (5):
        t.forward(100) 
        t.right(144)

    turtle.done()

def drawSpiral(t,linelen):
    """绘制螺旋"""
    if linelen>0:
        t.forward(linelen) 
        t.right(90)
        drawSpiral(t,linelen-10)

if __name__ == "__main__":
    #draw_forward()
    #draw_square()
    #draw_pentagram()

    t=turtle.Turtle()
    drawSpiral(t,200)
    turtle.done()

分形树:自相似递归图形

image

import turtle
def tree(t,branch_len):
    if branch_len>5:
        t.forward(branch_len)
        t.right(20)
        tree(t,branch_len-15)
        t.left(40)
        tree(t,branch_len-15)
        t.right(20)
        t.backward(branch_len)

if __name__ == "__main__":
    t=turtle.Turtle()
    t.left(90)
    t.penup()
    t.backward(100)
    t.pendown()
    t.pencolor("green")
    t.pensize(2)
    tree(t,75)
    t.hideturtle()
    turtle.done()

谢尔宾斯基三角

image

作图思路

  • degree=n
    • 三角形是由3个degree=n-1的三角形按照品字形拼叠而成
    • 这个3个degree=n-1的三角形边长均为degree=n的三角形的一半(规模减小)
  • degree=0
    • 等边三角形,这是递归的基本结束条件

代码

image

import turtle
def sierpinski(t, degree, points):
    """谢尔宾斯基三角"""
    colormap = ['blue', 'red', 'green', 'white', 'yellow', 'orange']
    drawTriangle(t, points, colormap[degree])
    if degree > 0:
        # 左边三角形
        sierpinski(t, degree-1,
                   {
                       'left': points['left'],
                       'top': getMid(points['left'], points['top']),
                       'right': getMid(points['left'], points['right'])
                   }
                   )
        # 顶部三角形
        sierpinski(t, degree-1,
                   {
                       'left': getMid(points['left'], points['top']),
                       'top': points['top'],
                       'right': getMid(points['top'], points['right'])
                   }
                   )
        # 右边三角形
        sierpinski(t, degree-1,
                   {
                       'left': getMid(points['left'], points['right']),
                       'top': getMid(points['top'], points['right']),
                       'right': points['right']
                   }
                   )

def drawTriangle(t, 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])/2, (p1[1]+p2[1])/2)

if __name__ == "__main__":
    t = turtle.Turtle()
    points = dict(
        left=(-200, -100),
        top=(0, 200),
        right=(200, -100)
    )
    sierpinski(t, 5, points)
    turtle.done()

汉诺塔

描述

  • 汉诺塔问题是法国数学家爱德华·卢卡斯于1883年发现的。他受到一个关于印度教寺庙的传说的启发,故事中这一问题交由年轻僧侣们解决。最开始,僧侣们得到三根杆子, 64个金圆盘堆叠在其中一根上, 每个圆盘比其下的小一点。僧侣们的任务是将64个圆盘从一根杆上转移到另一根杆上,但有两项重要的限制,一是他们一次只能移动一个圆盘,一是不能将大圆盘放在小圆盘之上。僧侣们日以继夜地工作,每秒移动一个圆盘。 传说中,当工作完成之时寺庙就会崩塌, 世界则将不复存在。
  • 传说很有趣,但也不用为世界末日将要到来而担心。毫无差错地完成这项工作需要264-1=18,446,774,073,709,551,615次移动。如果每秒移动一次则需要584,942,417,335(五千亿)年!显然实际上会更长

image

递归思路

  • 将盘片塔从开始柱,经由中间柱,移动到目标柱:
    1. 首先将上层N-1个盘片的盘片塔,从开始柱,经由目标柱,移动到中间柱
    2. 然后将第N个(最大)盘片,从开始柱,移动到目标柱
    3. 最后将放置在中间柱的N-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}")

if __name__ == "__main__":
    moveTower(3,"#1","#2","#3")

>>>
Moving disk[1] from #1 to #3
Moving disk[2] from #1 to #2
Moving disk[1] from #3 to #2
Moving disk[3] from #1 to #3
Moving disk[1] from #2 to #1
Moving disk[2] from #2 to #3
Moving disk[1] from #1 to #3

探索迷宫

原文

  • 古希腊迷宫
    • 克里特岛米诺斯王
    • 牛头人身怪物米诺陶洛斯
  • 圆明园 万花阵

image

算法思路

  • 步骤

    • 在初始位置尝试向北走一步,以此为开始递归程序。
    • 北面走不通的情况下则向南尝试,其后开始递归。
    • 南面走不通的情况下则向西尝试,其后开始递归。
    • 如果北面、南面、西面都走不通,则从东面开始并递归程序
    • 如果四个方向都走不出,则被困在迷宫中,以失败告终
  • 四种基本情况需要考虑:

    1、 海龟碰到“墙壁”,方格被占用无法通行。

    2、 海龟发现表示此方格已访问过,为避免陷入循环不在此位置继续寻找。

    3、 海龟碰到位于边缘的通道,即找到迷宫出口。

    4、 海龟在四个方向上探索都失败。

  • 递归归纳:

    • 海龟碰到“墙壁”方格,递归调用结束,返回失败
    • 海龟碰到“面包屑”方格,表示此方格已访问过,递归调用结束,返回失败
    • 海龟碰到“出口”方格,即“位于边缘的通道”方格,递归调用结束,返回成功!
    • 海龟在四个方向上的探索都失败,递归调用结束,返回失败

代码实现

image

  • 代码
import turtle
PART_OF_PATH = 'O'
TRIED = '.'
OBSTACLE = '+'
DEAD_END = '-'
class Maze:
    def __init__(self):
        self.mazelist = [
            ['+', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+','+', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+'],
            ['+', ' ', ' ', ' ', '+', ' ', ' ', ' ', '+', '+', ' ','+', '+', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
            [' ', ' ', ' ', ' ', ' ', ' ', '+', ' ', ' ', ' ', ' ',' ', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+'],
            ['+', ' ', '+', ' ', ' ', ' ', ' ', '+', '+', ' ', ' ','+', '+', '+', '+', ' ', '+', '+', '+', ' ', '+', '+'],
            ['+', ' ', '+', ' ', ' ', ' ', '+', ' ', '+', ' ', '+','+', ' ', ' ', ' ', ' ', '+', '+', '+', ' ', ' ', '+'],
            ['+', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ','+', '+', ' ', ' ', '+', '+', ' ', ' ', '+', ' ', '+'],
            ['+', '+', '+', '+', '+', ' ', '+', ' ', '+', ' ', ' ',' ', ' ', ' ', ' ', '+', '+', ' ', ' ', '+', ' ', '+'],
            ['+', '+', '+', '+', '+', ' ', '+', '+', '+', ' ', ' ','+', ' ', '+', ' ', ' ', '+', '+', ' ', ' ', ' ', '+'],
            ['+', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ','+', ' ', '+', ' ', 'S', '+', ' ', '+', ' ', ' ', '+'],
            ['+', '+', '+', '+', '+', ' ', '+', ' ', ' ', '+', ' ','+', ' ', '+', ' ', ' ', ' ', ' ', ' ', '+', ' ', '+'],
            ['+', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+','+', '+', '+', '+', '+', '+', '+', ' ', '+', '+', '+']
        ]
        self.startRow=8
        self.startCol=15
        self.rowsInMaze =len(self.mazelist)
        self.columnsInMaze =len(self.mazelist[0])
        self.xTranslate=-self.columnsInMaze/2
        self.yTranslate=self.rowsInMaze/2
        self.t = turtle.Turtle()
        self.t.shape('turtle')
        self.wn = turtle.Screen()
        self.wn.setworldcoordinates(-(self.columnsInMaze-1)/2-.5,
                                    -(self.rowsInMaze-1)/2-.5,
                                    (self.columnsInMaze-1)/2+.5,
                                    (self.rowsInMaze-1)/2+.5)

            

    def drawMaze(self):
        self.t.speed(10)
        self.wn.tracer(0)
        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')
        self.wn.update()
        self.wn.tracer(1)

    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):
    # 1.碰到墙壁,返回失败
    maze.updatePosition(startRow, startColumn)
    if maze[startRow][startColumn] == OBSTACLE :
        return False

    # 2. 碰到面包屑,或者死胡同,返回失败
    if maze[startRow][startColumn] == TRIED or maze[startRow][startColumn] == DEAD_END:
        return False

    # 3. 碰到出口,返回成功
    if maze.isExit(startRow,startColumn):
        maze.updatePosition(startRow, startColumn, PART_OF_PATH)
        return True

    # 4.洒下面包屑,继续探索
    maze.updatePosition(startRow, startColumn, TRIED)

    # 向北南西东4个方向依次探索,or操作符具有短路效应
    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

if __name__ == "__main__":
    myMaze = Maze()
    myMaze.drawMaze()
    myMaze.updatePosition(myMaze.startRow,myMaze.startCol)
    searchFrom(myMaze, myMaze.startRow, myMaze.startCol)
    turtle.done()
posted @ 2021-04-22 13:31  砥才人  阅读(309)  评论(0编辑  收藏  举报