zoukankan      html  css  js  c++  java
  • python之探索迷宫

      探索迷宫

        探讨一个与蓬勃发展的机器人领域相关的问题:走出迷宫。如果你有一个Roomba扫地机器人,或许

      能利用学到的知识对它进行重新编程。我们要解决的问题是帮助小乌龟走出虚拟的迷宫。迷宫问题源自忒修

      斯大战牛头怪的古希腊神话传说。相传,在迷宫里杀死牛头怪之后,忒修斯用一个线团找到了迷宫的出口。

      假设小乌龟被放置在迷宫里的某个位置,我们要做的是帮助它爬出迷宫,如图所示

        为简单起见,假设迷宫被分成许多格,每一格要么是空的,要么被墙堵上。小乌龟只能沿着空的

      格子爬行,如果遇墙,就必须转变方向。它需要如下的系统化过程来找到出路。

        (1)从起始位置开始,寿险向北移动一格,然后在新的位置再递归地重复本过程。

        (2)如果第一步往北行不通,就尝试向南移动一格,然后在递归重复本过程。

        (3)如果向南也行不通,就尝试向西移动一格,然后递归地重复本过程。

        (4)如果向北、向南、向西都不行,就尝试向东移动一格,然后递归地重复本过程。

        (5)如果四个方向都不行,就意味着没有出路。

        整个过程看上去非常简单,但是有许多细节需要讨论。假设递归过程的第一步是向北移动一格。

      根据上述过程,下一步也是向北移动一格。但是,如果北面有墙,必须根据递归过程的第二步向南移

      动一格。不行的是向南移动一格之后回到起点。如果继续执行该递归过程,就会又向北移动一格,然

      后又退回来,从而陷入无限循环中。所以,必须通过一格策略来记住到过的地方,本例假设小乌龟一

      边爬,一边丢面包屑。如果往某个方向走一格之后发现有面包屑,就知道应该退回去,然后尝试递归

      过程的下一步。查看这个算法的代码时会发现,退回去就是从递归函数调用中返回。

        和考察其他递归算法时一样,让我们来看看上述算法的基本情况,其中一些可以根据之前的描述

      猜到。这个算法需要考虑以下4种基本情况。

        (1)小乌龟遇到墙。由于格子被墙堵上,因此无法再继续探索。

        (2)小乌龟遇到了已经走过的格子。在这种情况下,我们不希望它继续探索,不然会陷入循环。

        (3)小乌龟找到了出口。

        (4)四个方向都行不通。

        为了使程序运行起来,需要通过一种方式标识迷宫。我们使用turtle模块来绘制和探索迷宫,以增

      加趣味性。迷宫对象提供下列方法,可用编写搜索算法。

        --init--读入一个代表迷宫数据文件,初始化迷宫的内部表示,并且找到小乌龟的起始位置。

        drawMaze在屏幕上的一个窗口中绘制迷宫。

        updatePosition更新迷宫的内部表示,并且修改小乌龟在迷宫中的位置。

        isExit检查小乌龟的当前位置是否为迷宫的出口。

        除此之外,Maze类还重载了索引运算符[ ],以便算法访问任一格的状态。

        以下代买清单展示了搜索函数searchFrom的代码。该函数接受3个参数:迷宫对象、起始行,以及

      起始列。由于该函数的每一次递归调用在逻辑上都是重新开始搜索的,因此定义接受3个参数非常重要。

    def searchFrom(maze, startRow, startColumn):
        # 从初始位置开始尝试四个方向,直到找到出路。
        # 1. 遇到障碍
        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
        maze.updatePosition(startRow, startColumn, TRIED)#更新迷宫状态、设置海龟初始位置并开始尝试
        # 4. 依次尝试每个方向
        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:                                                        #4个方向均是死胡同
            maze.updatePosition(startRow, startColumn, DEAD_END)
        return found

        该函数做的第一件事就是调用updatePosition(第2行)。这样做是为了对算法进行可视化,以便我们

      看到小乌龟如何在迷宫中寻找出口。接着,该函数检查前3中基本情况:是否遇到了墙(第5行)是否遇到

      了已经走过的格子(第8行)是否找到了出口(第11行)如果没有一种情况符合,则继续递归搜索。

        递归搜素调用了4个searchFrom。很难预测一共会进行多少个递归调用,这是因为它们都是用布尔运

      算符or连接起来的。如果第一次调用searchFrom后返回Ture,那么就不必进行后续的调用。可以这样理解:

      向北移动一格是离开迷宫的路径的一步。如果向北没有能够走出迷宫,那么尝试下一个递归调用,即向南移

      动。如果向南失败了,就尝试向西,最后向东。如果所有的递归调用都失败了,就说明遇到了死胡同。请下

      载或自己输入代码,改变4个递归调用的顺序,看看结果如何。

        Maze类的方法定义,--init--方法只接受一个参数,即文件名。该文本文件包含迷宫的信息,其中+代表

      墙,空格代表空格子,S代表起始位置。迷宫内部表示是一个列表,其元素也是列表。实例变量mazelist的每

      一行是一个列表,其中每一格包含一个字符。其内部如下。

       

        drawMaze方法使用以上内部表示在屏幕上绘制初始迷宫。

        updatePosition方法使用相同的内部表示检查小乌龟是否遇到墙。同时,它会更改内部表示,使用.和-来分

      别表示小乌龟遇到了走过的格子和死胡同。此外,updatePosition方法还使用辅助函数moveTurtle和dropBread

      crumb来更新屏幕上的信息。

        isExit方法检查小乌龟的当前位置是否为出口,条件是小乌龟已经爬到迷宫边缘:第0行、第0列、最后一行

      或者最后一列。

    import turtle
    
    # 迷宫类
    class Maze(object):
        # 读取迷宫数据,初始化迷宫内部,并找到海龟初始位置。
        def __init__(self, mazeFileName):
            rowsInMaze = 0                            #初始化迷宫行数
            columnsInMaze = 0                         #初始化迷宫列数
            self.mazelist = []                        #初始化迷宫列表
            mazeFile = open(mazeFileName, 'r')        #读取迷宫文件
            for line in mazeFile:                    #按行读取
                rowList = []                         #初始化行列表
                col = 0                             #初始化列
                for ch in line[:-1]:                #这样会丢失最后一列
    
                    rowList.append(ch)                #添加到行列表
                    if ch == 'S':                    #S为乌龟初始位置,即迷宫起点
                        self.startRow = rowsInMaze    #乌龟初始行
                        self.startCol = col         #乌龟初始列
                    col = col + 1                     #下一列
                rowsInMaze = rowsInMaze + 1         #下一行
                self.mazelist.append(rowList)        #行列表添加到迷宫列表
                columnsInMaze = len(rowList)         #获取迷宫总列数
            print(self.mazelist)
            self.rowsInMaze = rowsInMaze             #设置迷宫总行数
            self.columnsInMaze = columnsInMaze        #设置迷宫总列数
            self.xTranslate = -columnsInMaze/2         #设置迷宫左上角的初始x坐标
            self.yTranslate = rowsInMaze/2             #设置迷宫左上角的初始y坐标
            self.t = turtle.Turtle()                #创建一个海龟对象
            #给当前指示点设置样式(类似鼠标箭头),海龟形状为参数指定的形状名,指定的形状名应存在于TurtleScreen的shape字典中。多边形的形状初始时有以下几种:"arrow", "turtle", "circle", "square", "triangle", "classic"。
            self.t.shape('turtle')
            self.wn = turtle.Screen()                #创建一个能在里面作图的窗口
            # 设置世界坐标系,原点在迷宫正中心。参数依次为画布左下角x轴坐标、左下角y轴坐标、右上角x轴坐标、右上角y轴坐标
            self.wn.setworldcoordinates(-(columnsInMaze -1)/2 - 0.5, -(rowsInMaze - 1)/2 - 0.5, (columnsInMaze - 1)/2 + 0.5, (rowsInMaze - 1)/2 + 0.5)
    
        # 在屏幕上绘制迷宫
        def drawMaze(self):
            self.t.speed(20)                        #绘图速度
            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')
    
        # 画方块
        def drawCenteredBox(self, x, y, color):
            self.t.up()                                #画笔抬起
            self.t.goto(x - 0.5, y - 0.5)            #前往参数位置,此处0.5偏移量的作用是使乌龟的探索路线在单元格的正中心位置
            self.t.color(color)                        #方块边框为橙色
            self.t.fillcolor('green')                #方块内填充绿色
            self.t.setheading(90)                    #设置海龟的朝向,标准模式:0 - 东,90 - 北,180 - 西,270 - 南。logo模式:0 - 北,90 - 东,180 - 南,270 - 西。
            self.t.down()                            #画笔落下
            self.t.begin_fill()                        #开始填充
            for i in range(4):                        #画方块边框
                self.t.forward(1)                    #前进1个单位
                self.t.right(90)                    #右转90度
            self.t.end_fill()                        #结束填充
    
        # 移动海龟
        def moveTurtle(self, x, y):
            self.t.up()                                #画笔抬起
            # setheading()设置海龟朝向,towards()从海龟位置到由(x, y),矢量或另一海龟位置连线的夹角。此数值依赖于海龟初始朝向,由"standard"、"world"或"logo" 模式设置所决定。
            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(color)                        #dot(size=None, color)画路径圆点
    
        # 用以更新迷宫内的状态及在窗口中改变海龟位置,行列参数为乌龟的初始坐标。
        def updatePosition(self, row, col, val):
            self.mazelist[row][col] = val             #设置该标记状态为当前单元格的值
            self.moveTurtle(col, row)                #移动海龟
            if val == PART_OF_PATH:                 #其中一条成功路径的圆点的颜色
                color = 'green'
            elif val == TRIED:                        #尝试用的圆点的颜色
                color = 'black'
            elif val == DEAD_END:                    #死胡同用的圆点的颜色
                color = 'red'
            self.dropBreadcrumb(color)                #画路径圆点并上色
    
        # 用以判断当前位置是否为出口。
        def isExit(self, row, col):
            return (row == 0 or row == self.rowsInMaze - 1 or col == 0 or col == self.columnsInMaze - 1) #根据海龟位置是否在迷宫的4个边线位置判断
    
        # 返回键对应的值,影响searchFrom()中maze[startRow][startColumn]值的获取
        def __getitem__(self, key):
            return self.mazelist[key]
    
    if __name__ == '__main__':
        PART_OF_PATH = 'O'            #部分路径
        TRIED = '.'                    #尝试
        OBSTACLE = '+'                #障碍
        DEAD_END = '-'                #死胡同
        myMaze = Maze('maze.txt')#实例化迷宫类,maze文件是使用“+”字符作为墙壁围出空心正方形空间,并用字母“S”来表示起始位置的迷宫文本文件。
        myMaze.drawMaze()            #在屏幕上绘制迷宫。
        searchFrom(myMaze, myMaze.startRow, myMaze.startCol)    #探索迷宫

      

        

     

  • 相关阅读:
    正在与拖延症病魔抗争中
    Words For Today [20110724]
    短期目标[Till 20110805]
    Words For Today [20110804]
    Words For Today [20110731]
    Words For Today [20110801]
    Words For Today [20110723]
    《定位》一书
    马云的最近的话柱着拐杖跳高
    创业的人格
  • 原文地址:https://www.cnblogs.com/mtfan01/p/14958822.html
Copyright © 2011-2022 走看看