如何解决我的 8 个方格的 BFS 实现有什么问题
我设计了一个算法,可以使用 BFS 或 DFS 解决 8 平方问题。当前的问题是它运行了无限长的时间。如果我让它运行 30 分钟左右。它以我的 RAM 得到结束完整。我的实现有什么问题。我没有成功调试这段代码。 提前致谢。
import copy
import time
import pprint
def get_empty_board():
return [
[7,4,2],[8,3,0],[1,5,6]
]
end_state = [
[1,2,3],4],[7,6,5]
]
def solve_squares(board):
states_explored = list()
states = [ (neighbor,[board] + [neighbor]) for neighbor in get_neighbors(board) ]
while states:
print(len(states))
current_state = states.pop(0)
if (current_state[0]) in states_explored:
continue
# if len(states) > 300:
# states =[ states[0] ]
# pprint.pprint(current_state)
# pprint.pprint(current_state)
if current_state[0] == end_state:
return True,current_state[1]
neighbors = get_neighbors(current_state[0])
states_explored.append(current_state[0])
for neighbor in neighbors:
if (neighbor) not in states_explored:
states.append((neighbor,current_state[1] + [neighbor]))
states_explored.append((neighbor[0]))
return False,None
def get_neighbors(board):
x = None
y = None
neighbors = list()
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == 0:
x = i
y = j
break
# print(x,y)
for i in range(len(board)):
for j in range(len(board[0])):
if abs(i-x) <= 1 and abs(j-y) <= 1:
if abs(i-x) != abs(j-y):
# print(i,j)
new_state = copy.deepcopy(board)
new_state[x][y] = new_state[i][j]
new_state[i][j] = 0
# pprint.pprint(new_state)
# time.sleep(5)
neighbors.append(new_state)
return neighbors
def main():
result,path = solve_squares(get_empty_board())
print(result)
print(path)
main()
解决方法
您的解决方案需要进行一些改进:
- 您正在使用 Python 列表(例如,
states_explored
)来跟踪您已经访问过的电路板配置。现在,列表对于x in s
的平均案例复杂度是:O(n)。为此,您需要应用一些有效的数据结构(例如,set
)。您可以查看 this stack-overflow answer 以了解有关此优化的详细讨论。 - 您的 RAM 已满,因为您将每个发现的电路板配置的完整路径存储在队列中(例如,
states
)。这是非常不必要的并且内存效率低下。为了解决这个问题,您可以使用有效的数据结构(例如,map
)来存储给定状态的父状态。发现目标后,需要通过回溯地图构建路径。
我们可以通过以下示例将第二个优化可视化。假设您将 <state,parent-state>
映射为键值:
<initial-state,NaN>
<state-1,initial-state>
<state-2,state-1>
<state-3,state-2>
...
...
<final-state,state-n>
现在发现final-state
后,我们可以查询地图中final-state
的父节点是什么。然后,递归地进行这个查询,直到我们到达 initial-state
。
如果您应用这两项优化,您将在运行时间和内存消耗方面获得巨大改进。
,问题确实是性能。要了解速度,请将以下 print
放入您的代码(且仅此一个):
if len(states_explored) % 1000 == 0:
print(len(states_explored))
您会看到它在进行过程中是如何变慢的。我写了一个更有效的实现,发现算法需要访问超过 100,000 个状态才能找到解决方案。以您看到上述行输出行的速度,您可以想象需要 很长时间 才能完成。我没有耐心等待。
请注意,我在您的代码中发现了一个错误,但它不会损害算法:
states_explored.append((neighbor[0]))
这个说法是错误的,原因有两个:
-
neighbor
是一个棋盘,因此从中获取索引[0]
,生成该棋盘的第一行,这对于该算法是无用的。 - 如果您将其更正为仅
neighbor
,它会变得很重要,但会使算法停止运行,因为当这些邻居从队列中弹出时,搜索将停止。
所以这一行应该被省略。
以下是一些提高算法效率的方法:
- 使用原始值来表示板,而不是列表。例如,一个 9 个字符的字符串就可以完成这项工作。 Python 处理字符串比处理二维列表要快得多。这也意味着您不需要
deep_copy
。 - 不要使用列表来跟踪访问了哪些状态。使用一套——或者也涵盖下一点——一本字典。在集合/字典中查找比在列表中查找效率更高。
- 不要存储通向某个状态的整个电路板路径。跟踪之前的状态就足够了。您可以使用字典来指示访问了某个状态以及它来自哪个状态。这将把路径表示为一个链表。因此,一旦找到目标,您就可以从中重建路径。
- 寻找邻居时,不要迭代板的每个单元格。这些邻居在哪个索引上很清楚,最多有4个。只需定位这四个坐标并检查它们是否在范围内。在这里,棋盘的字符串表示也将派上用场:您可以使用
board.index("0")
定位 0 单元格。 - 不要在列表上使用
.pop(0)
:它效率不高。您可以改用deque
。或者 - 在这种情况下我更喜欢 - 根本不流行。相反,使用两个列表。迭代第一个,并填充第二个。然后将第二个列表分配给第一个列表,并使用空的第二个列表重复该过程。
这是我建议的代码。它具有我在本答案开头所建议的相同 print
,并在几秒钟内找到了解决方案。
def get_empty_board():
return "742830156"
end_state = "123804765"
def print_board(board):
print(board[:3])
print(board[3:6])
print(board[6:])
def solve_squares(board):
states = [(board,None)]
came_from = {}
while states:
frontier = []
for state in states:
board,prev = state
if board in came_from:
continue
came_from[board] = prev
if len(came_from) % 1000 == 0:
print(len(came_from))
if board == end_state: # Found! Reconstruct path
path = []
while board:
path += [board]
board = came_from[board]
path.reverse()
return path
frontier += [(neighbor,board) for neighbor in get_neighbors(board) if neighbor not in came_from]
states = frontier
def get_neighbors(board):
neighbors = list()
x = board.index("0")
if x >= 3: # Up
neighbors.append(board[0:x-3] + "0" + board[x-2:x] + board[x-3] + board[x+1:])
if x % 3: # Left
neighbors.append(board[0:x-1] + "0" + board[x-1] + board[x+1:])
if x % 3 < 2: # Right
neighbors.append(board[0:x] + board[x+1] + "0" + board[x+2:])
if x < 6: # Down
neighbors.append(board[0:x] + board[x+3] + board[x+1:x+3] + "0" + board[x+4:])
return neighbors
path = solve_squares(get_empty_board())
print("solution:")
for board in path:
print_board(board)
print()
,
展平棋盘并使用包含预映射动作的字典将大大简化和加速逻辑。建议使用 BFS 方法以获得最少的移动次数。为了跟踪访问过的位置,可以将展平的板存储为一个元组,这将允许直接使用一个集合来有效地跟踪和验证以前的状态:
# move mapping (based on position of the zero/empty block)
moves = { 0: [1,3],1: [0,2,4],2: [1,5],3: [0,4,6],4: [1,3,5,7],5: [2,8],6: [3,7: [4,6,8: [5,7] }
from collections import deque
def solve(board,target=(1,7,8,0)):
if isinstance(board[0],list): # flatten board
board = tuple(p for r in board for p in r)
if isinstance(target[0],list): # flatten target
target = tuple(p for r in target for p in r)
seen = set()
stack = deque([(board,[])]) # BFS stack with board/path
while stack:
board,path = stack.popleft() # consume stack breadth first
z = board.index(0) # position of empty block
for m in moves[z]: # possible moves
played = list(board)
played[z],played[m] = played[m],played[z] # execute move
played = tuple(played) # board as tuple
if played in seen: continue # skip visited layouts
if played == target: return path + [m] # check target
seen.add(played)
stack.append((played,path+[m])) # stack move result
输出:
initial = [ [7,2],[8,0],[1,6]
]
target = [ [1,[7,5]
]
solution = solve(initial,target) # runs in 0.19 sec.
# solution = (flat) positions of block to move to the zero/empty spot
[4,1,4]
board = [p for r in initial for p in r]
print(*(board[i:i+3] for i in range(0,9,3)),sep="\n")
for m in solution:
print(f"move {board[m]}:")
z = board.index(0)
board[z],board[m] = board[m],board[z]
print(*(board[i:i+3] for i in range(0,sep="\n")
[7,2]
[8,0]
[1,6]
move 3:
[7,3]
[1,6]
move 4:
[7,6]
move 7:
[0,6]
move 8:
[8,2]
[0,6]
move 1:
[8,2]
[1,3]
[0,6]
move 5:
[8,3]
[5,6]
move 4:
[8,6]
move 7:
[8,6]
move 8:
[0,6]
move 1:
[1,6]
move 7:
[1,2]
[7,6]
move 8:
[1,6]
move 2:
[1,0]
[7,6]
move 3:
[1,3]
[7,0]
[5,6]
move 6:
[1,6]
[5,0]
move 4:
[1,4]
move 5:
[1,6]
[0,4]
move 7:
[1,6]
[7,4]
move 8:
[1,3]
[8,4]
move 6:
[1,4]
move 4:
[1,4]
[7,0]
move 5:
[1,5]
move 6:
[1,5]
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。