回溯法——對部分解的深度優先搜尋(backtracking as DFS)
想像你在沒有地圖的情況下走迷宮。你一路向前,在每個岔口做選擇;走到死路時,你不會從頭來過——你退回最近一個還有沒試過岔路的路口,改走那條。這個退回的動作就是回溯。作為演算法,回溯一次做一個決定地建構解,而一旦某個部分選擇不可能通往任何好結果,它就撤銷最後那個選擇,改試下一個選項。
具體地說,回溯就是對部分解做深度優先搜尋。部分解是只建到一半的解——例如排列的前三個位置,或已擺好的前兩個皇后。演算法是遞迴的:若部分解已完整,就記錄下來;否則,對下一個決定的每個候選值,檢查加上它是否仍讓部分解可行;若可行,就加上、遞迴去做其餘決定,回來時再移除(撤銷),好讓下一個候選在乾淨狀態下被嘗試。這探索的是一棵節點為部分解、葉子為完整候選的樹,先深後廣——正是 DFS。「做—遞迴—撤銷」這個骨架在 N 皇后、子集合加總、圖著色與約束問題裡都一樣;只有可行性檢查會變。
整件事的重點、也是回溯勝過盲目列舉的原因,在於可行性檢查讓你能在延伸一個部分解之前就放棄它——一筆勾消一整棵候選子樹。若前兩個皇后已互相攻擊,你根本不會去擺其餘皇后,跳過底下所有葉子。最壞情況下回溯仍是指數的(當什麼都剪不掉時會退化成完整列舉),但在真實、受約束的實例上,剪枝往往把被探索的樹砍到極小的一部分,這正是它實用的原因。
一行一行地在棋盤上擺皇后。在第 0 行擺好一個皇后後,在第 1 行依序試列 0、1、2、...;每個都檢查它有沒有跟第 0 行的皇后同列或同對角線。衝突的列立刻被拒——對那個注定失敗的擺法,遞迴永遠不會往第 2、3、... 行下探。這種一再重複的提早拒絕,就是省下的功夫。
做一個選擇、遞迴、再撤銷它——並在某個部分選擇不可行的那一刻立即剪枝。
回溯的快慢全看它的剪枝:若可行性測試薄弱或缺席,它會探索整棵狀態空間樹,並不比暴力法好;聰明之處在那個檢查,不在遞迴本身。