Python BFS 程序不返回网格和路径,迷宫中有障碍物和解决方案

时间:2021-04-08 03:53:01

标签: python algorithm data-structures breadth-first-search maze

我已经导入了一个带有数字的文本文件,如下例:


3 0 0 0 0 1 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 2 3 3 0 3 0 0 0 0 3 3 3 0 3 0 0 0 0 3 3 3 0 3 2 2 0 0 3 3 3 3 3 3 3 3 2 3 3 3


目标是读入文本文件,将其格式化为我能够做到的网格(即 10 x 10 网格),然后对列表列表进行排序以找到数字 3 所在的解决方案一个障碍,数字 1 是起点,数字 2 是解决方案,我正在尝试使用 BFS 算法,其中代理可以向上、向下、向左、向右移动。

我正在尝试打印从起点(即 1)到达最接近的解决方案(即 2)所采取的步骤序列。数字格式为字符串/文本。我编写的程序似乎正在运行,但它从不打印解决方案或终止。要打印为解决方案的移动序列的格式为:


'下移' '提升' 等等。每个移动都在换行符上

我在下面附上了我的代码,非常感谢可以提供的任何帮助

    import queue


def read_user_input():
    file_name = input('Enter the name of your file :\n')
    return file_name


def read_to_grid():
    file_name = read_user_input()
    for nums in open(file_name):
        line = list(nums.split())
        result = []
        for _ in range(0, len(line), 10):
            result.append(line[_:_ + 10])
        return result
    file_name.close()


def print_grid(result, path=''):
    for x, pos in enumerate(result[0]):
        if pos == '0':
            start = x

    i = start
    j = 0
    pos = set()
    for move in path:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            j -= 1
        elif move == 'Move Down':
            j += 1

        pos.add((j, i))
    for j, row in enumerate(result):
        for i, col in enumerate(row):
            if (j, i) in pos:
                print('#', end='')
            else:
                print(col + ' ', end='')
        print()


def valid(result, moves):
    for x, pos in enumerate(result[0]):
        if pos == '0':
            start = x

    i = start
    j = 0
    for move in moves:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            j -= 1
        elif move == 'Move Down':
            j += 1
        if not (0 <= i < len(result[0]) and 0 <= j < len(result)):
            return False
        elif (result[i][j] == '3'):
            return False

    return True


def find_goal(result, moves):
    for x, pos in enumerate(result[0]):
        if pos == '0':
            start = x

    i = start
    j = 0
    for move in moves:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            j -= 1
        elif move == 'Move Down':
            j += 1

    if result[j][i] == '2':
        print('Found: ' + moves)
        print_grid(result, moves)
        return True

    return False


nums = queue.Queue()
nums.put('')
add = ''
result = read_to_grid()

while not find_goal(result, add):
    add = nums.get()
    for j in ['Move Left', 'Move Right', 'Move Up', 'Move Down']:
        put = add + j
        if valid(result, put):
            nums.put(put)

2 个答案:

答案 0 :(得分:0)

在调试您的代码时,当涉及到您的“valid”和“find_goal”函数时,我遇到了一些无限循环和其他错误。

根据我对广度优先搜索的经验,最好将每个点视为一个节点(在这种情况下是坐标),并使您的队列由当前正在尝试的路径列表组成。其中每条路径是横向的每个节点的列表。通常,您不想在给定路径中多次访问同一个节点,因此您必须跟踪此信息,而不仅仅是“左”、“右”等...

综上所述,我构建了您的代码并创建了一个函数,当给定一个考虑网格边界的节点时,该函数将返回有效的相邻节点,而不是 3 并且该节点是否已被访问。然后对于 BFS 部分,队列以包含起始节点的列表开始(我做了一个函数来查找 1 的位置)。然后当队列存在时,BFS 将从当前路径弹出,获取该路径中的最后一个节点,找到所有有效的相邻节点。对于每个有效的相邻节点,一个新的路径条目将被添加到由旧路径 + 相邻节点组成的队列中。如果相邻节点之一是目标,它将结束搜索并返回路径。我已经在路径中包含了方向信息,以便您可以解析它。

这应该打印到最近的 2 的路径:

[((5, 0), ''), ((5, 1), 'Down'), ((6, 1), 'Right'), ((6, 2), 'Down'), ((7, 2), 'Right'), ((7, 3), 'Down'), ((7, 4), 'Down'), ((7, 5), 'Down')]

您会看到 ...sorted(path_queue, key=lambda... 该行不需要,但它是一种确定队列优先级的懒惰方式,总是尝试最短的当前路径。如果你删除它,你会看到你仍然得到一个有效的路径,但它更长。

def read_user_input():
    file_name = input('Enter the name of your file :\n')
    return file_name


def read_to_grid():
    file_name = read_user_input()
    for nums in open(file_name):
        line = list(nums.split())
        result = []
        for _ in range(0, len(line), 10):
            result.append(line[_:_ + 10])

    int_result = []
    for i, row in enumerate(result):
        int_result.append([])
        for col in row:
            int_result[i].append(int(col))
    
    return int_result


def print_grid(result, path=''):
    for x, pos in enumerate(result[0]):
        if pos == 0:
            start = x

    i = start
    j = 0
    pos = set()
    for move in path:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            j -= 1
        elif move == 'Move Down':
            j += 1

        pos.add((j, i))
    for j, row in enumerate(result):
        for i, col in enumerate(row):
            if (j, i) in pos:
                print('#', end='')
            else:
                print(str(col) + ' ', end='')
        print()


def find_start_node(grid):
    for i, row in enumerate(grid):
        if 1 in row:
            return ((row.index(1), i), '')

    return (None, None)

def valid_adj(cur_node, grid, visited):
    x = cur_node[0][0]
    y = cur_node[0][1]
    
    adj = []

    if ((y + 1) < 10) and (grid[y + 1][x] != 3) and  not (any((x, y + 1) in node for node in visited)):
        adj.append(((x, y + 1), 'Down'))

    if ((x + 1) < 10) and (grid[y][x + 1] != 3) and not (any((x + 1, y) in node for node in visited)):
        adj.append(((x + 1, y), 'Right'))

    if ((y - 1) >= 0) and (grid[y - 1][x] != 3) and not (any((x, y - 1) in node for node in visited)):
        adj.append(((x, y - 1), 'Up'))

    if ((x - 1) >= 0) and (grid[y][x - 1] != 3) and not (any((x - 1, y) in node for node in visited)):
        adj.append(((x - 1, y), "Left"))

    return adj

def BFS(grid):
    start_node = find_start_node(grid)
    path_queue = [[start_node]]

    while path_queue:
        path_queue = sorted(path_queue, key=lambda x: len(x), reverse=True) # More optimized to guarantee shortest path, not needed

        cur_path = path_queue.pop()
        cur_node = cur_path[-1]

        if cur_node not in cur_path[:].pop():
            adj = valid_adj(cur_node, grid, cur_path)

            for node in adj:
                new_path = list(cur_path)
                new_path.append(node)

                path_queue.append(new_path)

                if grid[node[0][1]][node[0][0]] == 2:
                    print('path found')
                    return new_path

    return -1

grid = read_to_grid()

print_grid(grid)
print(BFS(grid))

答案 1 :(得分:0)

好的,Ryan 的回答已经说明了一切,但是这里是您的代码虽然效率不高,但我更改的唯一值得的事情是您可以使用列表而不是使用列表,并且有效函数现在检查行进路径,以便它知道它去过哪里,这样它就不会循环。

import queue

# Read name file from user
def read_user_input():
    file_name = input('Enter the name of your file :\n')
    return file_name

# Read file and return list of list[10]
def read_to_grid():
    with open(read_user_input()) as file:
        for nums in file:
            line = list(nums.split())
            return line

# Shows a text grid
def print_grid(result, path=[]):
    for x, pos in enumerate(result):
        if pos == '1':
            start = x

    i = start
    #j = 0
    pos = set()
    
    for move in path:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            i -= 10
        elif move == 'Move Down':
            i += 10

        pos.add(i)

    for i, celd in enumerate(result):
        if i % 10 == 0:
            print()
        if i in pos:
            print('# ', end='')
        else:
            print(celd + ' ', end='')      

# Validates coordinates and traveled path
def valid(result, moves):
    for x, pos in enumerate(result):
        if pos == '1':
            start = x

    i = start % 10
    j = start // 10

    # Where we start
    travel = [(j,i)]

    for move in moves:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            j -= 1
        elif move == 'Move Down':
            j += 1

        # Check if we have already been there
        if (j, i) in travel:
            return False
        else:
            travel += [(j,i)]

        # Check coordinates
        if i >= 10 or i < 0 or j >= len(result) // 10 or j < 0:
            return False
        elif result[i+j*10] == '3':
            return False

    return True

# Return true if 2 is reached
def find_goal(result, moves):
    for x, pos in enumerate(result):
        if pos == '1':
            start = x

    i = start
    #j = 0
    for move in moves:
        if move == 'Move Left':
            i -= 1
        elif move == 'Move Right':
            i += 1
        elif move == 'Move Up':
            i -= 10
        elif move == 'Move Down':
            i += 10

    if result[i] == '2':
        print('Found: ',' '.join(moves))
        print_grid(result, moves[0:-1])
        return True

    return False

nums = queue.Queue()
result = read_to_grid()
add = []

while not find_goal(result, add):
    if not nums.empty():
        add = nums.get()
    for j in ['Move Left', 'Move Right', 'Move Up', 'Move Down']:
        put = add + [j]
        if valid(result, put):
            nums.put(put)

编辑:

我清理了一点:

import queue

# Read name file from user
def read_user_input():
    file_name = input('Enter the name of your file :\n')
    return file_name

# Read file and return list of list[10]
def read_to_grid():
    with open(read_user_input()) as file:
        for nums in file:
            line = list(nums.split())
            return line

# Shows a text grid
def print_grid(result, path=[]):
    pos = set()
    
    for (x,y), _ in path:
        i = x + y*10
        pos.add(i)

    for i, celd in enumerate(result):
        if i % 10 == 0:
            print()
        if i in pos:
            print('# ', end='')
        else:
            print(celd + ' ', end='')      

# Validates coordinates and traveled path
def valid(result, moves):
    # Unpack
    (i,j), _ = moves[-1]

    # Check if already traveled
    if any(x == i and y == j for (x,y), __ in moves[:-1]):
        return False

    # Check coordinates
    if i >= 10 or i < 0 or j >= len(result) // 10 or j < 0:
        return False
    elif result[i+j*10] == '3':
        return False

    return True

# Return true if 2 is reached
def find_goal(result, moves):
    # Unpack
    (i,j), _ = moves[-1]

    if result[i+j*10] == '2':        
        #Print moves
        output = 'Found: '
        for (x,y), _ in moves:
            output += " "+_
        print(output)       
        #Print grid
        print_grid(result, moves[1:-1])

        return True
        
    return False

# Return new position and which movement was done.
def move(pos, dir):
    (x, y), _ = pos
    
    if dir == 'Move Left':
        x -= 1
    elif dir == 'Move Right':
        x += 1
    elif dir == 'Move Up':
        y -= 1
    elif dir == 'Move Down':
        y += 1
    
    return (x, y), dir 

nums = queue.Queue()
result = read_to_grid()

# Find the starting position
for x, pos in enumerate(result):
    if pos == '1':
        start = x

add = [((start % 10, start // 10),'')]

while not find_goal(result, add):
    if not nums.empty():
        add = nums.get()
    for j in ['Move Left', 'Move Right', 'Move Up', 'Move Down']:
        put = add + [move(add[-1],j)]
        if valid(result, put):
            nums.put(put)