带回溯的骑士之旅
Posted
技术标签:
【中文标题】带回溯的骑士之旅【英文标题】:Knights Tour with Backtracking 【发布时间】:2020-08-10 17:05:55 【问题描述】:我正在尝试编写一个 Python 程序,该程序将使用回溯解决骑士巡回赛问题。对于那些不熟悉的人:“骑士被放置在空棋盘的第一个方格上,根据国际象棋规则移动,必须准确地访问每个方格一次。”
我的代码大部分工作但不完全。它通常得到第 7 步,然后返回未完成的棋盘。为什么?
board = [
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]]
def move(x, y, count, move_x, move_y):
if count == len(board)**2:
return True
for i in range(8):
if is_valid(x + int(move_x[i]), y + int(move_y[i]), board):
if move(x + move_x[i], y + move_y[i], count + 1, move_x, move_y):
board[x][y] = count
return True
return False
def print_board(bo):
for i in range(len(bo)):
for j in range(len(bo[0])):
if j == (len(board[0]) - 1):
print(bo[i][j])
else:
print(bo[i][j], end = " ")
def is_valid(x, y, board):
if x >= 0 and y >= 0 and x < len(board) and y < len(board) and board[x][y] == 0:
return True
return False
def solve(bo):
print_board(board)
move_x = [1, 2, 2, 1, -1, -2, -2, -1]
move_y = [2, 1, -1, -2, -2, -1, 1, 2]
counter = 1
x_loc = 0
y_loc = 0
if not(move(x_loc, y_loc, counter, move_x, move_y)):
print("No solution")
else:
print(" ")
print_board(board)
solve(board)
【问题讨论】:
这是回溯,但不是完全递归的(或 OOP):board
是一个全局变量,move()
覆盖了全局变量 board
。而不是声明一个类 Board
和一个实例 board
具有可以保存和恢复的内部状态。因此,这通常无法"solve" Knights Tour,它只能找到第一个解决方案。
另外,move()
中的 for 循环总是返回它找到的第一个合法移动(即蛮力,它不适用,例如 Warnsdorff's heuristic: choose the square with the fewest legal onward moves,这将大大减少你的组合爆炸。而不是构建所有失败状态的哈希,这将使用大量的内存。
【参考方案1】:
对move()
的递归调用应该在设置board[x][y] = count
之后进行。
此外,如果move()
返回false,那么您应该有board[x][y] = 0
实际上,count
不需要存储在每个板单元中。它只需要在每个递归调用中传递和递增。您可以使用布尔值指示单元格是否已被访问。
最后的想法:有 2^64 个板子状态和更多重复命中失败状态的路径。因此,故障状态应存储在哈希中,以避免重复搜索故障路径。
【讨论】:
感谢塔里克的帮助!我已经实现了这个并且我的程序有效!但是,我相信您知道,它有点慢。您能否提供有关我应该如何将故障状态存储在哈希中的任何见解?我对此比较陌生,我不确定这意味着什么...... 很高兴听到你成功了。您可以将失败的板状态存储为包含所有单元格值的元组。一种更有效的存储方法是从每个板单元中找到的单个 True/False 值中生成一个 64 位无符号整数。然后将元组或长整数添加到集合中。在进行任何递归调用之前,请检查您所处的状态是否不属于失败状态 Set。当你走到死胡同时,存储失败的状态然后返回。 注意:Python 没有无符号类型,所以你必须将 0 到 2^64 的 64 位值映射到 -2^61 到 2^61 - 1 的范围内。 长整数由 64 位组成,您可以通过位运算符访问。一块板上有 64 个单元格。您可以用整数中的一位 (1/0) 表示每个单元格的值。例如,如果第 0 行第 2 列的值为 True,而第 5 行的第 3 列值为 True,则您最终会得到一个包含 64 个零的整数,但位位置 0 x 8 + 2 和位位置 5 x 8 + 3 除外a 1: 0000000000 0000000000 0000000000 0000001000 0000000000 0000000000 0000000000 0000000100 转换为十进制时得到 8796093022212 如果你以后想解决类似的问题,你应该分解问题并实现函数将板转换为整数并返回。一旦您单独测试该功能并确信它可以工作,您就可以将其集成到解决方案中。不是强迫你去做,只是想消除那种对你来说太复杂的压倒性感觉。【参考方案2】: def process(i,j,cnt,board,n,query):
board[i][j]=cnt
cnt+=1
if cnt==n*n+1:
for a in range(n):
print(board[a])
print()
if query==0:
return True
else:
True
stack=[]
if i+2<n and j-1>-1 and board[i+2][j-1]==-1:
stack.append([i+2,j-1])
if i+1<n and j-2>-1 and board[i+1][j-2]==-1:
stack.append([i+1,j-2])
if i-1>-1 and j-2>-1 and board[i-1][j-2]==-1:
stack.append([i-1,j-2])
if i-2>-1 and j-1>-1 and board[i-2][j-1]==-1:
stack.append([i-2,j-1])
if i-2>-1 and j+1<n and board[i-2][j+1]==-1:
stack.append([i-2,j+1])
if i-1>-1 and j+2<n and board[i-1][j+2]==-1:
stack.append([i-1,j+2])
if i+1<n and j+2<n and board[i+1][j+2]==-1:
stack.append([i+1,j+2])
if i+2<n and j+1<n and board[i+2][j+1]==-1:
stack.append([i+2,j+1])
while (len(stack))>0:
curr=stack.pop()
if query==0:
if(process(curr[0],curr[1],cnt,board,n,query)):
return True
else :
board[curr[0]][curr[1]]=-1
else:
process(curr[0],curr[1],cnt,board,n,query)
board[curr[0]][curr[1]]=-1
return
######## Driver Code ########
query=input("If you want program to output all possible ways of KNIGHT'S TOUR, say Yes or else say No :- ")
if query[0]=='y' or query[0]=='Y':
query=input("This process may be too slow for N greater than 5, it will work fast if N is less than 6. Are u sure u want to go for it? (Yes/No) :- ")
if query[0]=='y' or query[0]=='Y':
query=1
else :
query=0
n=int(input("Enter the value of N :- "))
if(n<5):
print("Not possible for N less than 5")
else:
board= [[-1]*n for i in range(n)]
cnt=1
process(0,0,cnt,board,n,query)
######## Some precomputed outputs ########
# N = 5
#
# [1, 12, 3, 18, 21]
# [4, 17, 20, 13, 8]
# [11, 2, 7, 22, 19]
# [16, 5, 24, 9, 14]
# [25, 10, 15, 6, 23]
# N = 6
#
# [1, 20, 3, 18, 5, 22]
# [36, 11, 28, 21, 30, 17]
# [27, 2, 19, 4, 23, 6]
# [12, 35, 10, 29, 16, 31]
# [9, 26, 33, 14, 7, 24]
# [34, 13, 8, 25, 32, 15]
# N = 7
#
# [1, 14, 3, 38, 5, 34, 7]
# [12, 39, 10, 33, 8, 37, 26]
# [15, 2, 13, 4, 25, 6, 35]
# [40, 11, 32, 9, 36, 27, 44]
# [19, 16, 21, 24, 45, 48, 29]
# [22, 41, 18, 31, 28, 43, 46]
# [17, 20, 23, 42, 47, 30, 49]
# N = 8
#
# [1, 60, 39, 34, 31, 18, 9, 64]
# [38, 35, 32, 61, 10, 63, 30, 17]
# [59, 2, 37, 40, 33, 28, 19, 8]
# [36, 49, 42, 27, 62, 11, 16, 29]
# [43, 58, 3, 50, 41, 24, 7, 20]
# [48, 51, 46, 55, 26, 21, 12, 15]
# [57, 44, 53, 4, 23, 14, 25, 6]
# [52, 47, 56, 45, 54, 5, 22, 13]
【讨论】:
这是 Knight's Tour 的另一个代码。它也适用于回溯的概念。 请edit你的答案来修正你的缩进。如果您可以添加更多解释,以便其他人可以理解您的解决方案,那也会很有帮助。 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。以上是关于带回溯的骑士之旅的主要内容,如果未能解决你的问题,请参考以下文章