簡體   English   中英

數獨解算器中的回溯失敗

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM