之前我們已經簡單地說明了怎樣用回溯演算法解決數獨的問題,思路如下:
從第乙個空格開始。依次嘗試 1 到 9 的數字,如果數字與盤面衝突就換成下乙個數字,如果不衝突就去往第二個空格;
在第二個空格,同樣依次嘗試 1 到 9 的數字,如果與盤面衝突就換成下乙個數字,如果不衝突就去往第三個空格,以此類推;
當最後乙個空格被成功填上數字時,我們將答案加入答案列表。
如果第乙個空格填 1 到 9 都不成立,那麼問題肯定沒有解,在這種情況下返回的結果列表為空。
數獨和 n 皇后十分相似,也是乙個「前進—後退—前進—後退」的過程,只不過 n 皇后是一行一行地嘗試,而數獨是乙個空格乙個空格地嘗試。因此我們同樣利用 helper() 方法幫助我們實現遞迴的**結構。
我們定義 helper(x) 方法去負責填滿第 x 格到最後一格的所有空格,因此它在填完當前格後會呼叫自己,讓 helper(x+1) 方法繼續填滿下乙個到最後乙個空格。
在解題過程中,我們直接用傳入的盤面作為我們動態的題解。盤面是乙個 9×9 的二維陣列,已知的數字被標記出來,空格用 0 表示。在改變盤面的時候,我們需要注意不能將已知數字與後期填的數字弄混。
我們還需要記錄當前空格的座標,用於改變盤面中的數字。我們有兩個選擇。
用長度為 2 的 [i,j] 陣列表示,如 [1,2] 代表第二行的第三個空格。
用乙個數字變數表示。如圖 1 所示,第 1 格的 index 是 0,第 2 格的 index 是 1,第 80 格的 index 是 79,第 81 格的 index 是 80。如果給定乙個座標,比如 55,通過除以 9 並取餘數,我們就能得知 55 代表的是第 7 行的第 2 個數。
圖 1:盤面中每乙個空格的座標
用數字變數記錄空格座標更方便,所以我們選擇這種方式。
我們定義 sudoku() 方法,輸入乙個 2 維陣列表示盤面,sudoku() 方法負責輸出所有可能的答案。
輸入盤面:
[[5 , 3 , 0 , 0 , 7 , 0 , 0 , 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 ]]
sudoku() 方法輸出:
[[5, 3, 4, 6, 7, 8, 9, 1, 2],
[6, 7, 2, 1, 9, 5, 3, 4, 8],
[1, 9, 8, 3, 4, 2, 5, 6, 7],
[8, 5, 9, 7, 6, 1, 4, 2, 3],
[4, 2, 6, 8, 5, 3, 7, 9, 1],
[7, 1, 3, 9, 2, 4, 8, 5, 6],
[9, 6, 1, 5, 3, 7, 2, 8, 4],
[2, 8, 7, 4, 1, 9, 6, 3, 5],
[3, 4, 5, 2, 8, 6, 1, 7, 9]]
以下是完整的**,其中,index 代表當前格的座標。copy.deepcopy() 是深度複製盤面的方法。
import copy
#輸出board所有的答案
def sudoku(board):
#檢查當前數字在盤面中是否成立
#i是當前行,j是當前列,num是當前數字
def checkboard(i,j,num):
for t in range(9): #檢查行
if t!=j and board[i][t] == num:
return false
if t!= i and board[t][j] == num: #檢查列
return false
for t in range(i-i%3, 3+i-i%3): #檢查3×3區域
for s in range(j-j%3, 3+j-j%3):
if t!=i and s!=j and board[t][s] == num:
return false
return true
#填滿盤面中第index格到最後乙個格
def helper(index):
if index==81: #邊界條件
solution= copy.deepcopy(board) #深度複製當前盤面,放到res列表裡
return #返回到上一次被呼叫的地方(第80個空格)
i = index//9 #當前行
j = index%9 #當前列
if board[i][j]==0: #如果當前格沒有已知數字
for num in range(1,10): #依次嘗試1~9
board[i][j] = num
if checkboard(i,j,num):
helper(index+1) #去往下乙個空格
board[i][j] = 0 #將空格重新變空
else: #如果當前格是盤面自帶的數字
helper(index+1)
res =
helper(0) #從第乙個空格開始
return res
數獨問題和 n 皇后問題的方法結構十分相似,都有 checkboard() 和 helper() 兩個子方法。checkboard() 的作用是檢查當前盤面是否成立。helper() 方法負責填滿從當前格到最後一格的所有空格。
checkboard() 方法的三個引數,分別是當前行、當前列和當前數字。因為我們每一次填乙個新空格的時候都會檢查盤面,所以在填當前數字之前的盤面毋庸置疑是成立的。因此,我們只需要檢查當前數字在當前行和當前列是不是獨一無二的,和當前數字在它屬於的 3×3 區域是不是獨一無二的就可以。
helper() 方法負責填滿當前格到最後一格的所有空格。它在填完當前格後會自己呼叫自己,讓 helper(index+1) 方法繼續填下乙個空格到最後乙個的所有空格。在填空格之前,它會首先檢查有沒有剩餘空格可填。如果 index<81,就說明有剩餘的空格。如果 index=81,盤面肯定已滿,這時我們將當前盤面深度複製,加入到結果列表。
def helper(index): #helper方法幫我們填滿盤面
if index==81: #如果盤面已滿,把當前盤面深度複製
solution= copy.deepcopy(board)
return #返回到上一次被呼叫的地方(第80個空格)
接下來我們來理解最後一行 return 的作用。return 表示從當前方法跳出來。
當前 index 等於 81,相當於我們在不存在的「第 82 格」裡。我們是從第 81 格來的,所以會被返回到第 81 格,那時我們最後一次呼叫 helper() 方法的地方。
因為第 81 格是最後乙個空格,所以它只有乙個可選擇數字。因此我們會走到 for 迴圈的盡頭,然後被返回到第 80 格。
如圖 2 所示,假設第 80 格中的當前數字是 7,這意味著我們還沒有嘗試過 8 和 9。從第 81 格跳出來後,遵循解法的步驟,我們會繼續嘗試 8 和 9。但是,這兩個數字肯定不滿足盤面,因為仔細想想,第 80 格也只能有乙個可選擇數字。8 和 9 嘗試失敗後,我們會返回到第 79 格。
圖 2:helper(80) 返回到 helper(79)(座標減1)
如圖 3 所示,假設第 79 格中的數字是 4,當我們從第 80 格跳回到第 79 格的時候,我們會從 5 繼續嘗試,再次去往第 80 格。
圖 3:helper(79) 返回到 helper(78)(座標減1)
return 最終的作用是確保演算法嘗試盤面所有可能,畢竟有些問題不止有乙個解。如果只想得到乙個解,那麼可以考慮 return 乙個布林值,那樣便可從整個方法中跳出來。
下面我們來看 index 不等於 81 時的情況。
for num in range(1,10): #依次嘗試1~9
board[i][j] = num
if checkboard(i,j,num): #如果數字成立
helper(index+1)
board[i][j] = 0 #把當前格還原成空格
我們先把數字填到盤面裡,board[i][j]=num,然後再做檢查,如果檢查不合格,就把格仔清空, board[i][j]=0,繼續嘗試下乙個數字。讀者可能會疑惑,為什麼要把格仔先清空再填數字,直接讓新數字覆蓋上乙個數字不是更方便嗎?
答案是,想象 1~9 都不可行的情況,這時當前格里的數字是 9。接著我們會退回到上一格嘗試下乙個數字。當我們嘗試 9 的時候,checkboard 會返回 false,因為當前格的 9 沒有被清除。這並不是我們期望看到的,因此我們需要加上 board[i][j]=0。當然,也可以這樣做:
for num in range(1,10):
board[i][j] = num
if checkboard(i,j,num): #當num等於1~8的時候,自動覆蓋
helper(index+1)
if num==9: #當num等於8的時候,清空格仔
board[i][j] = 0
數獨問題(DFS 回溯)
數獨遊戲的規則是這樣的 在乙個9x9的方格中,你需要把數字1 9填寫到空格當中,並且使方格的每一行和每一列中都包含1 9這九個數字。同時還要保證,空格中用粗線劃分成9個3x3的方格也同時包含1 9這九個數字。比如有這樣乙個題,大家可以仔細觀察一下,在這裡面每行 每列,以及每個3x3的方格都包含1 9...
C 解決數獨問題(回溯)
參考鏈結來自於 輸入乙個數獨作為9 9的陣列,例如輸入乙個測試資料map 9 9 為 0 9 0 0 0 0 0 6 0 8 0 1 0 0 0 5 0 9 0 5 0 3 0 4 0 7 0 0 0 8 0 7 0 9 0 0 0 0 0 9 0 8 0 0 0 0 0 6 0 2 0 7 0 0...
數獨遊戲(sudoku)演算法 回溯 剪枝
具體數獨遊戲是什麼,我就不介紹了,好像多餘了,你能來看這篇文章,說明你對數獨遊戲已經有了相當的了解了!還是入正題吧,今天先講解和發下用回溯 剪枝 求數獨遊戲,我也看了些回溯 剪枝求數獨的演算法,很惱火,既沒注釋,而且演算法沒有通用性。今天我給大家講的回溯 剪枝法,不僅可以用於解決數獨問題,而且還可以...