我将如何在这里实现回溯?

Posted

技术标签:

【中文标题】我将如何在这里实现回溯?【英文标题】:How would I implement backtracking here? 【发布时间】:2021-04-03 17:01:13 【问题描述】:

我一直在努力为我的数独求解器找到正确的逻辑。到目前为止,我已经创建了一个函数来获取 (x,y) 坐标和数字并检查它是否合法。

虽然我不明白如何遍历网格,将每个 0 替换为合法数字,然后返回并修复导致解不正确的数字。

例如,有时我会在网格上留下 0,因为某些数字无法再播放。我深入研究了一下,发现之前在同一行播放的数字是合法的 但破坏了这个谜题。相反,如果这些数字再高一点,难题就会得到解决。

我知道 backtracking 会是我的朋友,但是我不知道如何实现它。到目前为止,这就是我所拥有的。

solve.py

import numpy as np
import time
class sodoku:
    def __init__(self,grid,boxRange):
        self.grid = grid
        self.boxRange = boxRange
    def show(self):
        for row in self.grid:
            print(row)
    def solve(self):
        def possible(num,x,y):
            def box(x,y):                       
                board = np.array(self.grid)
                result = 
                size = 3
                for i in range(len(board) // size):
                    for j in range(len(board) // size):
                        values = board[j * size:(j + 1) * size, i * size:(i + 1) * size]
                        result[i * size + j + 1] = values.flatten()
                if y <= 2 and x <= 2:
                    squareBox = result[1]
                if (y <= 5 and y > 2) and x <= 2:
                    squareBox = result[2]
                if (y <= 8 and y > 5) and x <= 2:
                    squareBox = result[3]

                if (y <= 2 ) and (x <= 5 and x > 2):
                    squareBox = result[4]
                if (y <= 5 and y > 2)and (x <= 5 and x > 2):
                    squareBox = result[5]
                if (y <= 8 and y > 5)and (x <= 5 and x > 2):
                    squareBox = result[6]
            
                if (y <= 2) and (x <= 8 and x > 5):
                    squareBox = result[7]
                if (y <= 5 and y > 2)and (x <= 8 and x > 5):
                    squareBox = result[8]
                if (y <= 8 and y > 5)and (x <= 8 and x > 5):
                    squareBox = result[9]
                return squareBox
            row = self.grid[y]
            column= [r[x] for r in self.grid]
            square = box(x,y)
            if (num not in row) and (num not in column) and (num not in square):
                return True
            else:
                return False
        y = 0
        for row in self.grid:
            x = 0
            for number in row:
                if number == 0:
                    for i in range(1,10):
                        if possible(i,x,y):
                            row[x] = i
                        elif i == 9 and possible(i,x,y) == False: pass
                        #what would I do here now

                x += 1
            y += 1
      
boxRange = "3x3"           
bxd = []
with open('board.txt', 'r') as f:
    for line in f:
        line = line.strip()
        line = line.split(' ')
        bLine = [int(x) for x in line]
        bxd.append(bLine)
# brd = [[3,0,0,2],[0,4,1,0],[0,3,2,0],[4,0,0,1]]
brd = sodoku(bxd,boxRange)
brd.show()
brd.solve()
print('-----Solved------')
brd.show()

board.txt

5 3 0 0 7 0 1 0 0
6 0 0 1 9 5 0 0 0
0 9 8 0 0 0 0 6 0
8 0 0 0 6 0 0 0 3
4 0 0 8 0 3 0 0 1
7 0 0 0 2 0 0 0 6
0 6 0 0 0 0 2 8 0
0 0 0 4 1 9 0 0 5
0 0 0 0 8 0 0 7 9

【问题讨论】:

NameError:未定义名称“索引”。您可以尝试将用于每次移动的板保存到数组中。因此,如果您想要返回,只需在您想要的步骤中获取电路板并从中重新处理。 你能试着把这个想法变成代码吗?我无法理解你在说什么。 【参考方案1】:

递归伪代码回溯数独求解器:

#solve will return a solved board, or None if it fails
def solve(board):
    #case 1: board is solved
    if board.is_solved: #simple check for leftover empty spaces
        return board #board is solved. unzip the call stack

    pos = board.next_empty_space()
    valid = [i for i in range(1,10) if board.is_valid(pos, i)]

    #case 2: not solved and no more valid moves
    if not valid:
        return None #no valid moves left

    new_board = copy(board) #don't step on the original data in case this isn't the solution
    for value in valid:
        new_board[pos] = value
        result = solve(new_board)

        #case 3: one of the valid moves led to a valid solution
        if result is not None: #we found a fully solved board
            return result #here's where we unzip the call stack

    #case 4: none of the valid moves led to a valid solution
    return None #none of the valid moves panned out

基本上,您将板上的每个空白空间视为树中的一个分支,并从每个分支的子分支中插入一个当前在树中该点有效的新数字。如果您到达一个分支的末尾,并且没有更多有效的移动(子分支),那么您要么成功填写了所有空格,要么其中一个数字是错误的。当None 被返回,并且执行返回给调用者(调用堆栈中的上一帧),for 循环中的本地位置遍历有效移动就是“记住”你所在的位置,以及下一个可能的有效举动应该是。它基本上是一种用于正确棋盘状态的深度优先树搜索算法。

【讨论】:

EDIT 你必须小心这里的可变类型,并复制电路板以发送到递归函数,否则你可能会弄乱电路板状态以备将来调用的功能。

以上是关于我将如何在这里实现回溯?的主要内容,如果未能解决你的问题,请参考以下文章

排列 - DFS 和回溯 - 需要帮助理解展开和回溯

如何在 Scala 中停止回溯?

如何彻底避免正则表达式的灾难性回溯?

回溯尾递归算法可以转换为迭代吗?

PyQt5:如何将回溯显示到小部件中?

如何实现回溯打印背包中的物品(允许重复物品)?