带回溯的骑士​​之旅

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。

以上是关于带回溯的骑士​​之旅的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp 骑士之旅(回溯)

c ++中的递归回溯骑士之旅

我正在使用回溯解决骑士之旅问题,但我没有得到想要的结果

骑士之旅蛮力

骑士之旅 - 导致无限循环,我不知道为啥

平行骑士之旅算法