我在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
答案 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