已發布·持續改進
Algorithm
回溯法逐步建構解,一旦違反限制就撤銷選擇並回到上一步,藉由剪枝有效率地求解排列、組合、N 皇后與數獨等問題。
回溯法一步一步地填入答案,以深度優先的方式搜尋狀態空間樹;一旦目前的選擇違反限制,就退回上一步嘗試其他選項。程式碼由三個動作反覆組成:選擇一個候選,遞迴向下探索,返回時撤銷剛才的選擇。
與先產生所有完整候選再檢查的暴力搜尋不同,回溯法會在途中剪掉沒有希望的分支,往往能以少好幾個數量級的嘗試解決同一問題。它是列舉排列、組合,以及求解 N 皇后、數獨、圖著色等限制滿足問題的標準方法,也是正規表示式引擎與 SAT 求解器的基礎,因此在演算法面試與實務開發中都很常見。
建議先用子集合、排列這類不需要剪枝的列舉問題熟悉「選擇、探索、撤銷」的範本,再透過 N 皇后與數獨練習限制檢查與剪枝。接著親自計算走訪的節點數,體會剪枝的效果,並把同樣的問題與動態規劃比較,釐清什麼情況該選哪一種方法。
以空狀態為根、每次選擇為邊,所有候選都成為樹上的路徑,回溯法以深度優先走訪這棵樹。
把候選加入部分解,遞迴向下,返回時再移除,讓所有分支共用同一個串列。
目前的部分解不可能得到解時,就跳過其下方的整棵子樹,讓指數級的搜尋空間在實際上大幅縮小。
以集合或陣列記錄已占用的行、對角線、列或九宮格,就能在常數時間內判斷新的選擇是否違反規則。
permutations 以 used 陣列排除已選元素,依「選擇、探索、撤銷」產生所有排列,完成的路徑以 path[:] 複製後儲存。n_queens 以集合記錄直行與兩條對角線(row - c、row + c),立即跳過受攻擊的格子並計算解的個數。執行 python backtracking.py 會輸出 [1, 2, 3] 的 6 種排列與 8 皇后的解數 92。
backtracking.py
def permutations(items):
result, path = [], []
used = [False] * len(items)
def backtrack():
if len(path) == len(items):
result.append(path[:]) # store a copy of the full path
return
for i, x in enumerate(items):
if used[i]:
continue
used[i] = True # choose
path.append(x)
backtrack() # explore
path.pop() # unchoose
used[i] = False
backtrack()
return result
def n_queens(n):
cols, diag, anti = set(), set(), set()
def place(row):
if row == n:
return 1
count = 0
for c in range(n):
if c in cols or row - c in diag or row + c in anti:
continue # prune: square is attacked
cols.add(c); diag.add(row - c); anti.add(row + c)
count += place(row + 1)
cols.remove(c); diag.remove(row - c); anti.remove(row + c)
return count
return place(0)
print(permutations([1, 2, 3]))
print(n_queens(8)) # 92
python backtracking.py共六章,帶你從安裝一步步認識 回溯法 的核心概念。
在這裡提問、分享經驗,交流關於 回溯法 的看法。
還沒有討論。來發起第一則吧。
0 則留言
登入 · 登入後即可留言。
來留下第一則留言吧。