You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.
# n 皇后問題
!!! question
根據國際象棋的規則,皇后可以攻擊與同處一行、一列或一條斜線上的棋子。給定 $n$ 個皇后和一個 $n \times n$ 大小的棋盤,尋找使得所有皇后之間無法相互攻擊的擺放方案。
如下圖所示,當 $n = 4$ 時,共可以找到兩個解。從回溯演算法的角度看,$n \times n$ 大小的棋盤共有 $n^2$ 個格子,給出了所有的選擇 `choices` 。在逐個放置皇后的過程中,棋盤狀態在不斷地變化,每個時刻的棋盤就是狀態 `state` 。
![4 皇后問題的解 ](n_queens_problem.assets/solution_4_queens.png )
下圖展示了本題的三個約束條件:**多個皇后不能在同一行、同一列、同一條對角線上**。值得注意的是,對角線分為主對角線 `\` 和次對角線 `/` 兩種。
![n 皇后問題的約束條件 ](n_queens_problem.assets/n_queens_constraints.png )
### 逐行放置策略
皇后的數量和棋盤的行數都為 $n$ ,因此我們容易得到一個推論:**棋盤每行都允許且只允許放置一個皇后**。
也就是說,我們可以採取逐行放置策略:從第一行開始,在每行放置一個皇后,直至最後一行結束。
下圖所示為 4 皇后問題的逐行放置過程。受畫幅限制,下圖僅展開了第一行的其中一個搜尋分支,並且將不滿足列約束和對角線約束的方案都進行了剪枝。
![逐行放置策略 ](n_queens_problem.assets/n_queens_placing.png )
從本質上看,**逐行放置策略起到了剪枝的作用**,它避免了同一行出現多個皇后的所有搜尋分支。
### 列與對角線剪枝
為了滿足列約束,我們可以利用一個長度為 $n$ 的布林型陣列 `cols` 記錄每一列是否有皇后。在每次決定放置前,我們透過 `cols` 將已有皇后的列進行剪枝,並在回溯中動態更新 `cols` 的狀態。
那麼,如何處理對角線約束呢?設棋盤中某個格子的行列索引為 $(row, col)$ ,選定矩陣中的某條主對角線,我們發現該對角線上所有格子的行索引減列索引都相等,**即對角線上所有格子的 $row - col$ 為恆定值**。
也就是說,如果兩個格子滿足 $row_1 - col_1 = row_2 - col_2$ ,則它們一定處在同一條主對角線上。利用該規律,我們可以藉助下圖所示的陣列 `diags1` 記錄每條主對角線上是否有皇后。
同理,**次對角線上的所有格子的 $row + col$ 是恆定值**。我們同樣也可以藉助陣列 `diags2` 來處理次對角線約束。
![處理列約束和對角線約束 ](n_queens_problem.assets/n_queens_cols_diagonals.png )
### 程式碼實現
請注意,$n$ 維方陣中 $row - col$ 的範圍是 $[-n + 1, n - 1]$ , $row + col$ 的範圍是 $[0, 2n - 2]$ ,所以主對角線和次對角線的數量都為 $2n - 1$ ,即陣列 `diags1` 和 `diags2` 的長度都為 $2n - 1$ 。
```src
[file]{n_queens}-[class]{}-[func]{n_queens}
```
逐行放置 $n$ 次,考慮列約束,則從第一行到最後一行分別有 $n$、$n-1$、$\dots$、$2$、$1$ 個選擇,使用 $O(n!)$ 時間。當記錄解時,需要複製矩陣 `state` 並新增進 `res` ,複製操作使用 $O(n^2)$ 時間。因此,**總體時間複雜度為 $O(n! \cdot n^2)$** 。實際上,根據對角線約束的剪枝也能夠大幅縮小搜尋空間,因而搜尋效率往往優於以上時間複雜度。
陣列 `state` 使用 $O(n^2)$ 空間,陣列 `cols` 、`diags1` 和 `diags2` 皆使用 $O(n)$ 空間。最大遞迴深度為 $n$ ,使用 $O(n)$ 堆疊幀空間。因此,**空間複雜度為 $O(n^2)$** 。