python:递归法和回溯法求解迷宫问题以及二维矩阵中相同上下左右元素标记为同一部分(深度搜索变形问题)

1.递归法:先放程序: 

# 迷宫求解:分析和设计】
'''
问题分析:

问题表示:
       迷宫本身使用一个元素值为0/1的矩阵表示。迷宫入口、出口可以用一对下标表示。
    A: 为了防止程序在某些局部兜圈子,必须采用某种方法记录已经探查过的位置:两种方法(1)采用专门的结构记录这种信息
      (2)把已经探查过的标记在 迷宫上(将采用这种方式--把已经探查过的位置,对应矩阵元素标记为2,计算中发现位置值为2
       就不在探索了)(元素非0就是不能通过)。
    B: 确定当前可行位置方向的技术。位置(i,j)有4个相邻位置[(i,j+1),(i+1,j),(i,j-1),(i-1,j)],
       定义一个二元组的表,如dirs=[(0,1),(1,0),(0,-1),(-1,0)],可以通过将其与(i,j)相加得到四邻的位置。
    
    
'''
# 12*14
maze=[[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
      [1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1],
      [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1],
      [1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1],
      [1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1],
      [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1],
      [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
      [1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1],
      [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1],
      [1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1],
      [1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1],
      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],]

dirs = [(0,1),(1,0),(0,-1),(-1,0)]    #  位置(i,j)计算出四邻位置需要加的数对(东>南>西>北)
def mark(maze,pos):                  #  给迷宫maze的位置pos标2,表示已经探查过的位置
    maze[pos[0]][pos[1]] = 2
def passable(maze,pos):              #   检查迷宫maze的位置pos是否可行,可行返回True
    return maze[pos[0]][pos[1]] == 0

#  迷宫的递归求解】
'''
算法过程:
    1. mark当前位置(标记当前访问位置)
    2. 检查当前位置是否为出口,如果是则成功结束
    3. 逐个检查当前位置的4邻是否可以通达出口(递归调用自身)
    4. 如果对4邻的探索都失败,报告失败。
'''
def find_path(maze,pos,end):
    mark(maze,pos)
    if pos == end:                                      #  基线条件--已经达到出口
        print(pos,end='')                               #  输出这个位置
        return True                                    #  成功结束
    for i in range(4):                                  #  否则按照4个方向顺序探查
        nextp = pos[0]+dirs[i][0],pos[1]+dirs[i][1]
        #  考虑下一个可能方向
        if passable(maze,nextp):                        #  不可行的相邻位置不算
            if find_path(maze,nextp,end):
                print(pos,end='')
                return True
    return False

print(np.shape(maze))
print("迷宫:", find_path(maze,pos=(1,1),end=(10,12)))
for i in range(12):
    print(maze[i])
    
'''
#  运行结果
(12, 14)
(10, 12)(9, 12)(8, 12)(7, 12)(6, 12)(5, 12)(5, 11)(5, 10)(6, 10)(6, 9)(6, 8)(6, 7)(6, 6)(6, 5)(6, 4)(6, 3)(7, 3)(7, 2)(7, 1)(6, 1)(5, 1)(4, 1)(3, 1)(2, 1)(1, 1)迷宫: True
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 2, 2, 2, 1, 1, 2, 2, 2, 1, 0, 0, 0, 1]
[1, 2, 1, 2, 2, 2, 2, 1, 2, 1, 0, 1, 0, 1]
[1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 0, 1, 0, 1]
[1, 2, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 1]
[1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1]
[1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1]
[1, 2, 2, 2, 1, 1, 1, 0, 1, 0, 1, 1, 2, 1]
[1, 0, 1, 2, 1, 2, 1, 0, 1, 0, 1, 0, 2, 1]
[1, 0, 1, 2, 1, 2, 1, 0, 1, 1, 1, 1, 2, 1]
[1, 0, 1, 2, 2, 2, 1, 0, 0, 1, 0, 0, 2, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

'''

2.以一个简单的例子来分析递归过程,如下图是一个5*5的迷宫地图(1表示非通道,0表示通道):

1 0 1 1 1
1 0 0 0 1
1 0 1 0 1
0 0 1 0 1
1 1 1 1 1

 (S、N、W、E)分别为南、北、西、东四个方向,f_p(5)代表递归函数,5*5的迷宫递归简要过程如下图所示:

 3.回溯法:这种算法在工作中执行两种基本动作:前进和后退

前进:

          条件:若当前位置存在尚未探查的四邻位置。则继续前进。

                   操作:选择下一个位置并向前探查。若还存在其他未探查的分支,就记录相关信息(记录当前位置,以及未探查分支的相对位置),以便将来使用。如果找到出口,则成功结束。

后退(回溯):

         条件:若遇到死路,不存在尚未探查的四邻位置。则回溯。

                  操作:找回最近记录的那个位置信息,检查是否还存在未探索的分支,若有就取一个,未探索的位置作为当前位置并前进;若没有并将其删除并继续回溯。 

                  若已穷尽所有可能:不能找到出口时以失败结束。

4.代码如下:

#  栈和回溯法解决迷宫问题
def maze_solve(maze,start,end):
    if start == end:                        #   若开始位置即为结束位置时,打印位置信息并返回
        print(start)
        return
    st = sStack()                           #   申请一个栈对象,栈的实现请查看https://blog.csdn.net/weixin_39781462/article/details/82290886
    mark(maze,start)                        #   标记开始位置
    st.push((start,0))                      #   开始位置入栈
    while not st.is_empty():               #    直到栈内元素为空才出循环
        pos,nxt = st.pop()                  #    取出栈中元素
        for i in range(nxt,4):             #     取
            nextp = (pos[0]+dirs[i][0],     #     下一个要处理的位置
                     pos[1]+dirs[i][1])
            if passable(maze, nextp):       #    位置元素满足通过要求
                st.push((pos,i+1))          #    位置入栈--存入栈的是序对(pos.nxt),其中分支点位置用pos(行列坐标对)表示,nxt是正数表示回溯到该位置后需要探索的下一位置方向
                mark(maze,nextp)            #    标记位置
                st.push((nextp,0))          #    位置入栈
                if nextp == end:            #    位置为end时,输出栈内元素,即为寻找的路径
                    while not st.is_empty():
                        print(st.pop()[0], end='')
                    return
                break                      #     退出内层循环,下次迭代将以新栈顶为当前位置继续
    print("No path found")                #     找不到路径


maze=[[1, 0, 1, 1, 1],
      [1, 0, 0, 0, 1],
      [1, 0, 1, 0, 1],
      [0, 0, 1, 0, 1],
      [1, 1, 1, 1, 1]]

print("回溯法解迷宫问题:")
maze_solve(maze,start=(0,1),end=(3,0))

'''
回溯法解迷宫问题:
(3, 0)(3, 1)(2, 1)(1, 1)(0, 1)
'''

5.题目描述:给定一个M*M的二维数组,每个值1的元素 代表一个团队,如果两个团队的上下或左右方向相邻,说明两个团队有紧密合作的关系,可以形成一个部门,没有合作关系的团队放到不同的部门,判定给定输入中有多少个部门?

例如,给定一个二维数组:

1 0 0 1 1
1 0 0 1 1
0 0 1 0 0
0 0 1 0 0
0 0 1 0 0

 其中有九个团队,其中有3个部门(已经使用不同的颜色进行标记)。

解题思路:符合一个部门的条件是:可以相串连的一些团队可以称为一个部门,因此我们可以将问题转化为迷宫房间位置的个数统计(位置为1代表房间,位置为0代表墙壁),每个房间是由一个或多个1组成,一个房间的查找我们可以使用【迷宫搜索路线的思想--使用栈和回溯】,将收到的房间位置标记为2,然后再进行下一个房间的搜索,统计搜索次数记为所求。

使用栈和回溯求解:

#----------------------------------对maze矩阵预先处理,添加0边--------------------------------------------------------
maze=[ [0, 1, 0, 0, 1],
       [1, 1, 1, 0, 1],
       [0, 1, 0, 0, 0],
       [1, 0, 0, 1, 0],
       [0, 1, 1, 1, 1] ]

def maze_add2(maze):              #  为原矩阵maze添加一圈0元素
    maze2 = np.zeros((len(maze)+2,len(maze)+2),dtype=np.int32)
    for i in range(1,len(maze)+1):
        for j in range(1,len(maze)+1):
            maze2[i][j] = maze[i-1][j-1]
    return maze2
maze1 = maze_add2(maze)

#-----------------------------使用栈和回溯方法进行上下左右区域块标记为2-----------------------------------------------
dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)]  # 位置(i,j)计算出四邻位置需要加的数对(东>南>西>北)
def mark(maze, pos):  # 给maze的位置pos标2,表示已经探查过的位置
    maze[pos[0]][pos[1]] = 2
def passable(maze, pos):  # 检查maze的位置pos是否可行,可行返回True
    return maze[pos[0]][pos[1]] == 1

#  使用栈和回溯法标记位以start为开始的连接区域块
def maze_solve(maze,start):
    st = sStack()                           #   申请一个栈对象,栈的实现请查看https://blog.csdn.net/weixin_39781462/article/details/82290886
    mark(maze,start)                        #   标记开始位置
    st.push((start,0))                      #   开始位置入栈
    while not st.is_empty():               #    直到栈内元素为空才出循环
        pos,nxt = st.pop()                  #    取出栈中元素
        for i in range(nxt,4):             #
            nextp = (pos[0]+dirs[i][0],     #     下一个要处理的位置
                     pos[1]+dirs[i][1])
            if passable(maze, nextp):       #    位置元素满足通过要求
                st.push((pos,i+1))          #    位置入栈--存入栈的是序对(pos.nxt),其中分支点位置用pos(行列坐标对)表示,nxt是正数表示回溯到该位置后需要探索的下一位置方向
                mark(maze,nextp)            #    标记位置
                st.push((nextp,0))          #    位置入栈
                break                      #     退出内层循环,下次迭代将以新栈顶为当前位置继续
    print("marked the value of the start'department")                #     找不到路径

#   为矩阵添加一圈0元素
for i in range(len(maze1)):
    print(maze1[i])

#------------------------------------------遍历矩阵中每一个元素-------------------------------------------------------
num=0
for i in range(len(maze1)):
    for j in range(len(maze1)):
        if maze1[i][j] == 1:
            maze_solve(maze1, start=(i, j))
            print("\n")
            num +=1
print("\n")
for i in range(len(maze1)):
    print(maze1[i])

print("maze中部门的数量为:",num)
'''
#  输出
[0 0 0 0 0 0 0]
[0 0 1 0 0 1 0]
[0 1 1 1 0 1 0]
[0 0 1 0 0 0 0]
[0 1 0 0 1 0 0]
[0 0 1 1 1 1 0]
[0 0 0 0 0 0 0]
marked the value of the start'department

marked the value of the start'department

marked the value of the start'department

marked the value of the start'department

[0 0 0 0 0 0 0]
[0 0 2 0 0 2 0]
[0 2 2 2 0 2 0]
[0 0 2 0 0 0 0]
[0 2 0 0 2 0 0]
[0 0 2 2 2 2 0]
[0 0 0 0 0 0 0]
maze中部门的数量为: 4

'''

猜你喜欢

转载自blog.csdn.net/weixin_39781462/article/details/82430256