在数独求解器中的回溯失败

时间:2016-02-14 01:11:52

标签: python recursion sudoku

我在Python中编写了一个数独求解器,它接收了一个部分填充的板,并使用回溯和前向检查来填补其余部分并解决难题。前向检查是每次为空白单元格分配值时,检查其行,列和框是否未分配"邻居"转让后仍有非空的域名。

为了代表我的电路板(尺寸:带有P x Q框的N x N板),我使用的是一个2D列表,其中每个条目的格式为[value,[domain]],其中value是指定的单元格编号(如果未指定,则为0),域是可保持数独谜题一致的单元格的可能值。

我相信我的递归错误,但无法弄清楚是什么。该函数始终失败并返回False。下面是我的递归求解器函数的一部分,取出了预处理代码。如有必要,我可以发布整个函数及其辅助函数。

def fc_recursive_solve(board, N, P, Q, row, col, outputFile, timeout):
    ###some preprocessing here to check if puzzle is solved and find next empty cell if not

    vals = board[row][col][1] #domain/possible values for the empty cell
    for num in vals:
        #if num doesn't violate the row, col, and box sudoku constraints
        if constraintCheck(row, col, num, P, N, Q, board):
            #make copy of cell's domain for backtracking
            tempDomain = copy.deepcopy(board[row][col][1])

            board[row][col][0] = num        #assign num to the cell

            #remove num from domains of neighbors,
            #if an empty domain results after removing num, 
            #return False and the original board,
            #else return True and the updated board
            noEmptyDomains, board = propagate_fc(board, N, P, Q, row, col, num)
            if noEmptyDomains:
                board[row][col][1] = [num]  #update domain to be num and then recurse
                if fc_recursive_solve(board, N, P, Q, row, col, outputFile, timeout):
                    return True
                #backtrack -- reset value and domain of assigned cell
                board[row][col][1] = tempDomain
                board[row][col][0] = 0
            else:
                board[row][col][0] = 0
    return False

编辑:更多代码并尝试Blckknght的解决方案

def fc_recursive_solve(board, N, P, Q, row, col, outputFile, timeout):
    if time.clock() >= timeout:
        return "timeout"

    while row < N and board[row][col][0] != 0: #find next blank
        if col == N-1:
            row = row + 1
            col = 0
        else:
            col = col + 1

    if row == N: #solved
        return True

    for num in vals:
        if constraintCheck(row, col, num, P, N, Q, board):
            board[row][col][0] = num
            noEmptyDomains, new_board = propagate_fc(board, N, P, Q, row, col, num) # new var
            if noEmptyDomains:
                new_board[row][col][1] = [num]   # this doesn't modify board, only new_board
                if fc_recursive_solve(new_board, N, P, Q, row, col, outputFile, timeout):
                    return True
            board[row][col][0] = 0   # the only thing that's required to backtrack
    return False

def propagate_fc(board, N, P, Q, row, col, num):
    origBoard = copy.deepcopy(board)
    #row propagate
    for x in range(N):
        if board[x][col][0] == 0:
            if num in board[x][col][1]:
                board[x][col][1].remove(num)
        if len(board[x][col][1]) == 0:
            return False, origBoard #domain is empty; return original board

    #col propagate
    for y in range(N):
        if board[row][y][0] == 0:
            if num in board[row][y][1]:
                board[row][y][1].remove(num)
        if len(board[row][y][1]) == 0:
            return False, origBoard #domain is empty

    #box propagate
    rDiv = row/P
    cDiv = col/P
    for i in range((rDiv * P), ((rDiv + 1) * P)):
        for j in range((cDiv * Q), ((cDiv + 1) * Q)):
            if board[i][j][0] == 0:
                if num in board[i][j][1]:
                    board[i][j][1].remove(num)
            if len(board[i][j][1]) == 0:
                return False, origBoard #domain is empty

    return True, board #success; return board with updated domains

2 个答案:

答案 0 :(得分:0)

如果你正在进行回溯,你需要能够将你的棋盘状态恢复到以前的状态。您当前的代码没有这样做。 propagate_fc函数以不易撤消的方式修改board

由于我没有看到一种简单的方法来反转propagate_fc的逻辑,我建议更改解算器的设计,以便它复制电路板并仅修改复制品,然后再将它们传递给递归步骤。如果副本没有导致解决方案,它可以被丢弃,而不是尝试编写回溯代码来修复它。

(我确定 可能会回溯,但是不清楚什么是跟踪变化的好方法,并且找出它对于这个答案来说太多了。)< / p>

无论如何,这是我对解算器修改版本的建议:

def fc_recursive_solve(board, N, P, Q, row, col, outputFile, timeout):
    if time.clock() >= timeout:
        return "timeout"

    while row < N and board[row][col][0] != 0: #find next blank
        if col == N-1:
            row = row + 1
            col = 0
        else:
            col = col + 1

    if row == N: #solved
        return board

    for num in vals:
        if constraintCheck(row, col, num, P, N, Q, board):
            new_board = copy.deepcopy(board)
            new_board[row][col][0] = num
            if propagate_fc(new_board, N, P, Q, row, col, num):
                new_board[row][col][1] = [num] 
                result = fc_recursive_solve(new_board, N, P, Q, row, col, outputFile, timeout)
                if result is not None and result != "timeout":
                    return result
        return None

如果成功,我已将其更改为返回已解决的电路板。否则,您会收到True响应,但无法看到结果(因为顶级代码的board不会被修改)。

以下是propagate_fc的更改版本:

def propagate_fc(board, N, P, Q, row, col, num):
    # no copying any more here

    #row propagate
    for x in range(N):
        if board[x][col][0] == 0:
            if num in board[x][col][1]:
                board[x][col][1].remove(num)
        if len(board[x][col][1]) == 0:
           return False

    #col propagate
    for y in range(N):
        if board[row][y][0] == 0:
            if num in board[row][y][1]:
                board[row][y][1].remove(num)
        if len(board[row][y][1]) == 0:
            return False

    #box propagate
    rDiv = row/P
    cDiv = col/P
    for i in range((rDiv * P), ((rDiv + 1) * P)):
        for j in range((cDiv * Q), ((cDiv + 1) * Q)):
            if board[i][j][0] == 0:
                if num in board[i][j][1]:
                    board[i][j][1].remove(num)
            if len(board[i][j][1]) == 0:
                return False

    return board #success; return new board

这里唯一真正的变化是我不打扰返回board,因为我们总是在适当的位置修改它。只需要bool结果(如果董事会是否有效)。

(我最初认为还有另一个问题,每个区块都有if len(...) == 0个检查,但事实证明这并不是一个问题。只有当你做这些检查时,你可能会获得稍微好一点的表现。来自当前remove列表的board[x][y][1] da值(通过将这些块缩进两个级别),但它不太可能是一个很大的性能提升。)

答案 1 :(得分:0)

基于快速浏览,我认为你混合了你的行/ col传播:

#row propagate
for x in range(row):                <== should this be range(col) ?
    if board[row][x][0] == 0:       <== row is fixed, but cols (x) changes
        if num in board[row][x][1]:
            board[row][x][1].remove(num)
    if len(board[row][x][1]) == 0:
        return False, origBoard #domain is empty; return original board