[英]Backtracking in sudoku solver failing
我正在用Python寫一個數獨求解器,它使用了部分填充的板,並使用回溯和向前檢查來填充其余部分並解決難題。 向前檢查是每次將值分配給空白單元格時,都要檢查分配后的行,列和框未分配的“鄰居”是否仍具有非空域。
為了表示我的木板(尺寸:N x N木板,帶有P x Q框),我使用的是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
如果您正在回溯,則需要能夠將電路板的狀態恢復到之前的狀態。 您當前的代碼沒有這樣做。 propagate_fc
函數以一種不容易撤消的方式修改board
。
因為我沒有看到一個簡單的方法來扭轉的邏輯propagate_fc
,我建議改變求解器的設計,使得它使板的副本,只有將它們傳遞給進一步的遞歸步驟之前修改副本。 如果該副本無法解決問題,則可以將其丟棄,而不必嘗試編寫回溯代碼來對其進行備份。
(我敢肯定有可能回溯,只是跟蹤變化的好方法並不明顯,而弄清楚這個答案的工作量太大。)
無論如何,這是我對求解器的修改版本的建議:
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
,但結果證明這根本不是問題。僅執行這些檢查可能會獲得更好的性能。當您剛剛從當前board[x][y][1]
列表中remove
da值時(通過將這些塊縮進兩個級別),但是不太可能獲得很大的性能提升。)
快速瀏覽一下,我認為您混合了行/列傳播:
#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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.