回溯演算法python 數獨問題 回溯演算法經典例題

2021-10-11 11:24:52 字數 4856 閱讀 5076

之前我們已經簡單地說明了怎樣用回溯演算法解決數獨的問題,思路如下:

從第乙個空格開始。依次嘗試 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)演算法 回溯 剪枝

具體數獨遊戲是什麼,我就不介紹了,好像多餘了,你能來看這篇文章,說明你對數獨遊戲已經有了相當的了解了!還是入正題吧,今天先講解和發下用回溯 剪枝 求數獨遊戲,我也看了些回溯 剪枝求數獨的演算法,很惱火,既沒注釋,而且演算法沒有通用性。今天我給大家講的回溯 剪枝法,不僅可以用於解決數獨問題,而且還可以...