回溯法是個很無聊的死算方法,沒什么技巧,寫這篇博客主要原因是以前思路不太清晰,現在突然想用回溯法解決一個問題時,無法快速把思路轉換成代碼。
-------------------------------------------------------------------------------------------------------------------------------------
N-皇后問題描述:在N*N的棋盤上,每一行放置一個皇后,使得任意皇后之間不能互相攻擊。求放置方法。
(因為國際象棋中皇后可以走橫豎斜線,所以相當於是任意2個棋子不處在同一行、列或對角線)
思路是設解為四維向量x,第i行把皇后放在第x[i]列。(這里把行號列號均從0開始)然后像下面這樣一個個找:
初始:x為空。令x[0] = 0,然后尋找第1個符合約束(簡稱“合法”)的x[1],可得x[1] = 2。再尋找第1個合法的x[2],發現無論是0、1、2、3都不行。於是就得回退了。
退到x[1],尋找下一個符合約束的x[1],即令x[1]=3,再繼續找x[2]。若找到一組解則回退,尋找下一組解,一直到無法回退為止。
(比如x = { 1, 3, 0, 2 },添加x到解集中,之后回退尋找下一個合法的x[2]。此時發現1、2、3都不行,於是再回退,尋找下一個合法的x[1]。)
#include <stdio.h>
#include <vector>
#include <array>
using namespace std;
template <int N>
class QueenProblem
{
public:
explicit QueenProblem()
{
for (int i = 0; i < N; i++)
x[i] = -1;
run();
}
size_t size() const { return results.size(); }
const array<int, N>& operator[](size_t k) const { return results[k]; }
private:
vector<array<int, N>> results; // 解集
// 解向量, (i,x[i])代表第i行第x[i]列放置皇后
// 其中行號和列號都是從0開始, 即范圍為[0,N)
// x[i]=-1則代表第i行的位置並未確定
array<int, N> x;
void run()
{
int k = 0;
while (k >= 0)
{
x[k]++; // 嘗試新的位置
while (x[k] < N && !CheckRow(k))
x[k]++;
if (x[k] == N) // 當前行無法放置皇后, 回溯
{
x[k--] = -1;
continue;
}
if (k == N - 1) // 找到一組解, 將其添加到解集中並回溯尋找新的解
{
results.emplace_back(x);
x[k--] = -1;
}
else // 第0到k行合法, 嘗試設置第k+1行的皇后
{
k++;
}
}
}
// 假設第0到k-1行均成功放置皇后並且合法(即其中任意2個皇后不處於同一行/列/對角線)
// 判斷第k行的放置方案是否合法, 若合法則返回true, 否則返回false
bool CheckRow(int k)
{
for (int i = 0; i < k; i++)
if (x[i] == x[k] || abs(x[i] - x[k]) == abs(i - k))
return false;
return true;
}
};
int main()
{
#define PrintNQueenSolNum(N) printf("%2d皇后的解的數量: %7ld\n", N, \
QueenProblem<N>().size());
PrintNQueenSolNum(1);
PrintNQueenSolNum(2);
PrintNQueenSolNum(3);
PrintNQueenSolNum(4);
PrintNQueenSolNum(5);
PrintNQueenSolNum(6);
PrintNQueenSolNum(7);
PrintNQueenSolNum(8);
PrintNQueenSolNum(9);
PrintNQueenSolNum(10);
PrintNQueenSolNum(11);
PrintNQueenSolNum(12);
PrintNQueenSolNum(13);
PrintNQueenSolNum(14);
PrintNQueenSolNum(15);
printf("其中, 4皇后的解為:\n");
QueenProblem<4> sol;
for (size_t i = 0; i < sol.size(); i++)
{
printf("第%d組解: ", i);
for (int x : sol[i])
printf("%d ", x);
printf("\n");
}
return 0;
}

直接貼包裝后的代碼了,編譯期確定N,所以不支持運行時確定N是多少。算到15皇后就完了。
體現回溯法的核心代碼就是成員函數run()
