使用递归在Python中解决迷宫问题

3
所以,我有一个任务要求我使用递归来解决迷宫问题。我将发布任务指南,以便您可以看到我在谈论什么。教授没有太多解释递归,他给了我们递归的例子,我会发布这些例子,但我希望有人能够更深入地解释递归以及如何应用它来解决迷宫问题。我不是要求任何人编写代码,我只是希望一些解释能让我走上正确的道路。感谢任何回答的人。
以下是我拥有的例子:
    def foo():
        print("Before")
        bar()
        print("After")

    def bar():
        print("During")


    def factorial(n):
        """n!"""
        product = 1
        for i in range(n,0,-1):
        product *= i
        return product

    def recFac(n):
        """n! = n * (n-1)!"""
        if(n == 1):
          return 1
        return n * recFac(n-1)

    def hello():
        """Stack overflow!"""
        hello()

    def fib(n):
        """f(n) = f(n-1) + f(n-2)
        f(0) = 0
        f(1) = 1"""
        if n == 0 or n == 1: #base case
           return n
        return fib(n-1) + fib(n-2) #recursive case

    def mult(a,b):
        """a*b = a + a + a + a ..."""
        #base case
        if (b == 1):
           return a
        #recursive case
        prod = mult(a,b-1)
        prod *= a
        return prod


    def exp(a,b):
        """a ** b = a* a * a * a * a *.... 'b times'"""
        #base case
        if (b==0):
           return 1
        if (b == 1):
           return a
        #recursive case
        return exp(a,b-1)*a

    def pallindrome(word):
        """Returns True if word is a pallindrome, False otherwise"""
        #base case
        if word == "" or len(word)==1:
           return True

        #recursive case
        if word[0] == word[len(word)-1]:
        word = word[1:len(word)-1]
        return pallindrome(word)
        else:
            return False

以下是指南:
您将创建一个迷宫爬行器,利用递归的力量解决任何迷宫问题!
问题1-加载迷宫
在解决迷宫之前,您需要先加载它。对于此任务,您将使用迷宫的简单文本格式。您可以使用此示例迷宫或创建自己的迷宫。
您的目标是加载任何给定的迷宫文件,并将其读入二维列表中。例如:loadMaze(“somemaze.maze”)应加载somemaze.maze文件并创建以下列表...
    [['#','#','#','#','#','#','#','#','#'], 
     ['#','S','#',' ',' ',' ','#','E','#'], 
     ['#',' ','#',' ','#',' ',' ',' ','#'], 
     ['#',' ',' ',' ','#',' ','#',' ','#'], 
     ['#', #','#','#','#','#','#','#','#']] 

请注意,列表已去除所有的 '\r' 和 '\n' 字符。为了使下一个问题更简单,您可以将此列表设置为全局变量。
接下来编写一个函数,以更美观的格式打印迷宫:
例如:
    ####################################
    #S#  ##  ######## # #      #     # #
    # #   #             # #        #   #
    #   # ##### ## ###### # #######  # #
    ### # ##    ##      # # #     #### #
    #   #    #  #######   #   ###    #E#
    ####################################

在进行后续操作之前,请使用不同的迷宫测试您的代码。

问题2-准备解决迷宫

在解决迷宫之前,您需要找到起点!在您的代码中添加一个名为findStart()的函数,该函数将逐个字符搜索迷宫,并返回“S”字符的x和y坐标。您可以假定迷宫中最多存在一个这样的字符。如果在迷宫中未找到“S”,请将x和y坐标都返回-1。

在进行后续操作之前,请在多个位置(包括没有位置)测试代码中的“S”。

问题3-解决迷宫!

最后,您已经准备好通过递归解决迷宫了!您的解决方案应仅需要一个方法:solve(y,x)。

单个solve方法实例应解决迷宫中的单个位置。参数y和x是要解决的当前坐标。您的solve方法应完成以下几项任务。它应检查是否正在解决'E'位置。在这种情况下,您的solve方法已成功完成。否则,它应尝试递归解决右侧的空格。请注意,您的方法应仅尝试解决空格,而不是墙('#')。如果该递归未导致结束,则尝试向下,然后向左和向上。如果所有这些都失败了,则您的代码应回溯一步,并尝试另一个方向。

最后,在解决迷宫时,您的代码应留下其进度的指示器。如果搜索右侧,则当前位置应有'>'代替空格。如果向下搜索,请放置“v”。如果向左搜索,则是“<”,如果向上搜索,则是“^”。如果您的代码必须回溯,则删除方向箭头,并将位置设置回“ ”。

一旦解决了迷宫,请再次打印迷宫。您应该看到逐步走迷宫的指南。

    main("somemaze.maze")
    ######### 
    #S#   #E# 
    # # #   # 
    #   # # # 
    #########

S在(1,1)处。

     ######### 
     #S#>>v#E# 
     #v#^#>>^# 
     #>>^# # # 
     #########

用不同的起始点和终止点测试您的代码,可选择在各种迷宫中进行测试。

这是我目前为止的代码: 但是代码实际上没有在迷宫中打印出轨迹,我不确定原因在哪里。

    def loadMaze():
        readIt = open('Maze.txt', 'r')
        readLines = readIt.readlines()
        global mazeList
        mazeList = [list(i.strip()) for i in readLines]

    def showMaze():
        for i in mazeList:
            mazeprint = ''
        for j in i:
            mazeprint = mazeprint + j
        print(mazeprint)
        print('\n')    

    def solve(x,y, mazeList):
        mazeList[x][y] = "o"
        #Base case  
        if y > len(mazeList) or x > len(mazeList[y]):
           return False
        if mazeList[y][x] == "E":
           return True 
        if mazeList[y][x] != " ":
           return False
        #marking
        if solve(x+1,y) == True:  #right
           mazeList[x][y]= '>'
        elif solve(x,y+1) == True:  #down
             mazeList[x][y]= 'v'     
        elif solve(x-1,y) == True:  #left
             mazeList[x][y]= '<'     
        elif solve(x,y-1) == True:  #up
             mazeList[x][y]= '^'
        else:
           mazeList[x][y]= ' '
        return (mazeList[x][y]!= ' ')

1
可能有帮助的链接:https://dev59.com/-37aa4cB1Zd3GeqPlxmt - sshashank124
谢谢!我一直在寻找例子,但是我没有找到一个对我真正有用的!这个看起来非常有帮助。 - MarissaLeigh
没问题。如果你遇到其他问题,只需更新问题并提出新的疑问即可。 - sshashank124
这段时间,虽然与迷宫解决无关,但我尝试在stackoverflow上展示/解释了一个递归问题。http://stackoverflow.com/a/22678614/478656 - TessellatingHeckler
有人能告诉我为什么我更新的代码可能无法正常工作吗? - MarissaLeigh
5个回答

2
你可以把解决迷宫的过程看作是走步。
每次走步时,都遵循相同的规则。因为每次都遵循相同的规则,所以你可以使用完全相同的指令集来处理每一步。当你走一步时,只需再次调用相同的例程,更改参数以指示新步骤。这就是递归。通过一步一步地进行,您可以将问题分解。
注意:有些递归解决方案将问题分成两半,独立地解决每个部分,这在两个解决方案实际上是独立的情况下有效。但是,在此处不起作用,因为每个步骤(解决方案)都取决于先前的步骤。
如果你走到了死路,就会回退到死路,直到找到一个有可行方块的步骤。
提示:你不会在通往出口的路上标记正确的路径,因为你不知道你现在走的步骤是否是通向出口的路径的一部分。你会在回程路上标记路径,因为你知道每一步都确实是路径的一部分。你可以这样做,因为每一步都记得它在下一步之前在哪个方块中。
相反,你在每个你尝试过的方块上放一个标记,只是说:我已经来过这里了,不需要再次检查这个方块。在打印解决方案之前清除它们。

2

这是我对CodeEval的The Labirynth挑战的解决方案:

import sys
sys.setrecursionlimit(5000)


class Maze(object):
    FLOOR = ' '
    WALLS = '*'
    PATH = '+'

    def __init__(self):
        self.cols = 0
        self.rows = 0
        self.maze = []

    def walk_forward(self, current_k, r, c):
        self.maze[r][c] = current_k
        next_k = current_k + 1
        # up
        if r > 1:
            up = self.maze[r - 1][c]
            if up != self.WALLS:
                if up == self.FLOOR or int(up) > current_k:
                    self.walk_forward(next_k, r - 1, c)
        # down
        if r < self.rows - 1:
            down = self.maze[r + 1][c]
            if down != self.WALLS:
                if down == self.FLOOR or int(down) > current_k:
                    self.walk_forward(next_k, r + 1, c)
        # left
        if c > 1:
            left = self.maze[r][c - 1]
            if left != self.WALLS:
                if left == self.FLOOR or int(left) > current_k:
                    self.walk_forward(next_k, r, c - 1)
        # right
        if c < self.cols - 1:
            right = self.maze[r][c + 1]
            if right != self.WALLS:
                if right == self.FLOOR or int(right) > current_k:
                    self.walk_forward(next_k, r, c + 1)

    def walk_backward(self, r, c):
        current_k = self.maze[r][c]
        if not isinstance(current_k, int):
            return False
        self.maze[r][c] = self.PATH

        up = self.maze[r - 1][c] if r > 0 else None
        down = self.maze[r + 1][c] if r < self.rows - 1 else None
        left = self.maze[r][c - 1] if c > 1 else None
        right = self.maze[r][c + 1] if c < self.cols else None

        passed = False
        if up and isinstance(up, int) and up == current_k - 1:
            self.walk_backward(r - 1, c)
            passed = True
        if down and isinstance(down, int) and down == current_k - 1:
            self.walk_backward(r + 1, c)
            passed = True
        if left and isinstance(left, int) and left == current_k - 1:
            self.walk_backward(r, c - 1)
            passed = True
        if right and isinstance(right, int) and right == current_k - 1:
            self.walk_backward(r, c + 1)                    

    def cleanup(self, cleanup_path=False):
        for r in range(0, self.rows):
            for c in range(0, self.cols):
                if isinstance(self.maze[r][c], int):
                    self.maze[r][c] = self.FLOOR
                if cleanup_path and self.maze[r][c] == self.PATH:
                    self.maze[r][c] = self.FLOOR

    def solve(self, start='up', show_path=True):
        # finding start and finish points
        upper = lower = None
        for c in range(0, self.cols):
            if self.maze[0][c] == self.FLOOR:
                upper = (0, c)
                break
        for c in range(0, self.cols):
            if self.maze[self.rows - 1][c] == self.FLOOR:
                lower = (self.rows - 1, c)
                break
        if start == 'up':
            start = upper
            finish = lower
        else:
            start = lower
            finish = upper

        self.cleanup(cleanup_path=True)
        self.walk_forward(1, start[0], start[1])
        length = self.maze[finish[0]][finish[1]]
        if not isinstance(length, int):
            length = 0
        if show_path:
            self.walk_backward(finish[0], finish[1])
            self.cleanup(cleanup_path=False)
        else:
            self.cleanup(cleanup_path=True)
        return length

    def save_to_file(self, filename):
        with open(filename, 'w') as f:
            f.writelines(str(self))

    def load_from_file(self, filename):
        self.maze = []
        with open(filename, 'r') as f:
            lines = f.readlines()
        for line in lines:
            row = []
            for c in line.strip():
                row.append(c)
            self.maze.append(row)
        self.rows = len(self.maze)
        self.cols = len(self.maze[0]) if self.rows > 0 else 0

    def get_maze(self):
        return copy.copy(self.maze)

    def __str__(self):
        as_string = u''
        for row in self.maze:
            as_string += u''.join([str(s)[-1] for s in row]) + "\n"
        return as_string


maze = Maze()
maze.load_from_file(sys.argv[1])
maze.solve(show_path=True)
print str(maze)

LMAO。这不是很严肃的事情...只是一个简单的深度优先搜索哈哈。你需要放松一下,也许去睡觉一下。 - Max Eisenhardt
@MaxEisenhardt 我刚刚复制粘贴了一个先前实现的解决方案来解决类似任务。 - Ihor Pomaranskyy
再说一遍,这只是一个对二维矩阵的简单DFS操作。不需要超过200行的代码哈哈!伙计,别太紧张了,去睡个好觉吧。你尝试得太努力了! - Max Eisenhardt
@MaxEisenhardt兄弟,我毫不怀疑可能会有更好的解决方案。我的方案是根据两个要求编写的——适应编码挑战平台的输入输出期望,并且在低级别上易读易懂。顺便说一句,展示你的解决方案,你会让这个社区更好。 :) - Ihor Pomaranskyy
https://dev59.com/_37aa4cB1Zd3GeqPlxqt#69289232 - Max Eisenhardt
@MaxEisenhardt 不错。 - Ihor Pomaranskyy

1
import os

class Maze_Crawler:
    def __init__(self):
        self.maze = []
        
    def load_maze(self, path):
        
        rows = []
        
        with open(path, 'r') as f:
            rows = f.readlines()
        
        for i in range(len(rows)):
            self.maze.append([])
        
            for j in range(len(rows[i])-1):
                self.maze[i].append(rows[i][j])
        
        return self.maze
    
    def get_start_coor(self):
        for i in range(len(self.maze)):
            for j in range(len(self.maze[i])):
                if self.maze[i][j] == 'S':
                    return i, j
        return -1, -1
    
    def solve_maze(self, coor):
        x, y = coor
        
        if self.maze[x][y] == '#' or self.maze[x][y] == 'X':
            return False
        
        if self.maze[x][y] == 'E':
            return True
        
        
        if self.maze[x][y] != 'S':
            self.maze[x][y] = 'X'
        
        if self.solve_maze((x+1, y)):
            if self.maze[x][y] != 'S':
                self.maze[x][y] = 'v'
            
        elif self.solve_maze((x-1, y)):
            if self.maze[x][y] != 'S':
                self.maze[x][y] = '^'
            
        elif self.solve_maze((x, y+1)):
            if self.maze[x][y] != 'S':
                self.maze[x][y] = '>'
            
        elif self.solve_maze((x, y-1)):
            if self.maze[x][y] != 'S':
                self.maze[x][y] = '<'
        else:
            return False
        
        return True
      
    def show_solution(self):
        for i in range(len(self.maze)):
            r = ''
            for j in range(len(self.maze[i])):
                if self.maze[i][j] == 'X':
                    r +=  ' '
                else:
                    r += self.maze[i][j]
            print(r)

0
递归实际上是一个简单的想法:为了解决一个问题,你将问题缩小一步,然后解决减小后的问题。这个过程持续进行,直到达到一个你知道如何完全解决的“基本问题”。你返回基本解决方案,然后在每个步骤中添加到返回的解决方案,直到你拥有完整的解决方案。
因此,为了解决n!,我们记住n并解决(n-1)!的值。基本情况是1!,我们返回1;然后在每个返回步骤中,我们乘以记住的数字(2 * 1! 是2,3 * 2! 是6,4 * 3! 是24,5 * 4! 是120),直到我们乘以n并获得完整的解决方案。这实际上是一种相当苍白和贫血的递归类型;每个步骤只有一种可能的决策。被称为“尾部递归”,非常容易反转并转换为迭代解决方案(从1开始,乘以每个数字直到n) 。

一种更有趣的递归方式是将问题分成两半,解决每个部分,然后组合两个部分的解决方案;例如,快速排序通过选择一个项目,将列表分成“比项目小的所有内容”和“比项目大的所有内容”,对每个部分进行快速排序,然后返回 quicksorted(smaller) + item + quicksorted(larger)。基本情况是“当我的列表只有一个项目时,它已经排序好了”。

对于迷宫问题,我们将把问题分成四个方向 - 如果我从当前位置向右、左、上和下走,所有可能的解决方案 - 其中只有一个递归搜索会找到解决方案。基本情况是“我站在 E 上”,失败是“我在墙上”或“我在一个我已经访问过的空间上”。


编辑:出于兴趣,这里提供一个面向对象的解决方案(适用于Python 2.x和3.x):

from collections import namedtuple

Dir = namedtuple("Dir", ["char", "dy", "dx"])

class Maze:
    START = "S"
    END   = "E"
    WALL  = "#"
    PATH  = " "
    OPEN  = {PATH, END}  # map locations you can move to (not WALL or already explored)

    RIGHT = Dir(">",  0,  1)
    DOWN  = Dir("v",  1,  0)
    LEFT  = Dir("<",  0, -1)
    UP    = Dir("^", -1,  0)
    DIRS  = [RIGHT, DOWN, LEFT, UP]

    @classmethod
    def load_maze(cls, fname):
        with open(fname) as inf:
            lines = (line.rstrip("\r\n") for line in inf)
            maze  = [list(line) for line in lines]
        return cls(maze)

    def __init__(self, maze):
        self.maze = maze

    def __str__(self):
        return "\n".join(''.join(line) for line in self.maze)

    def find_start(self):
        for y,line in enumerate(self.maze):
            try:
                x = line.index("S")
                return y, x
            except ValueError:
                pass

        # not found!
        raise ValueError("Start location not found")

    def solve(self, y, x):
        if self.maze[y][x] == Maze.END:
            # base case - endpoint has been found
            return True
        else:
            # search recursively in each direction from here
            for dir in Maze.DIRS:
                ny, nx = y + dir.dy, x + dir.dx
                if self.maze[ny][nx] in Maze.OPEN:  # can I go this way?
                    if self.maze[y][x] != Maze.START: # don't overwrite Maze.START
                        self.maze[y][x] = dir.char  # mark direction chosen
                    if self.solve(ny, nx):          # recurse...
                        return True                 # solution found!

            # no solution found from this location
            if self.maze[y][x] != Maze.START:       # don't overwrite Maze.START
                self.maze[y][x] = Maze.PATH         # clear failed search from map
            return False

def main():
    maze = Maze.load_maze("somemaze.txt")

    print("Maze loaded:")
    print(maze)

    try:
        sy, sx = maze.find_start()
        print("solving...")
        if maze.solve(sy, sx):
            print(maze)
        else:
            print("    no solution found")
    except ValueError:
        print("No start point found.")

if __name__=="__main__":
    main()

并且运行时会生成:

Maze loaded:
    ####################################
    #S#  ##  ######## # #      #     # #
    # #   #             # #        #   #
    #   # ##### ## ###### # #######  # #
    ### # ##    ##      # # #     #### #
    #   #    #  #######   #   ###    #E#
    ####################################
solving...
    ####################################
    #S#  ##  ######## # #>>>>>v#  >>v# #
    #v#>>v#    >>>v     #^#   >>>>^#>>v#
    #>>^#v#####^##v######^# #######  #v#
    ### #v##>>>^##>>>>>v#^# #     ####v#
    #   #>>>^#  #######>>^#   ###    #E#
    ####################################

请注意,按照给定的方式进行分配有一些非Pythonic元素:
  • 它要求使用camelCase函数名称而不是underscore_separated
  • 它建议使用全局变量而不是显式传递数据
  • 它要求find_start在失败时返回标志值而不是引发异常

0

使用Python解决迷宫问题展示了我的答案。然而,如果你想自己编写代码,步骤如下:

 1. Start at the entrance.  
 2. Call the function solve(x,y) with the entrance co-ordinates  
 3. in solve, return false if the input point has already been handled or is a wall.  
 4. Mark the current point as handled (tag = 'o')  
 5. go to the right and call solve on that point. If it returns true, set tag to '>'  
 6 elif do the same for left and '<'  
 7 elif do the same for up and '^'  
 8 elif do the same for down and 'v'  
 9 else this is a false path, set tag = ' ' 
10 set the current maze point to tag
11 return (tag != ' ')

或者可以跳过第9步,直接进行第11步。

return(tag != 'o')

然后搜索迷宫并将每个 'o' 替换为 ' '

您可以以两种方式显示迷宫,以便显示您尝试解决问题的方式以及最终答案。这已被用作Solaris屏幕保护程序,其中潜在路径以一种颜色显示,而实际路径以不同的颜色显示,以便您可以看到它尝试并成功。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接