一.問題分析
N×N的棋盤上有N個皇后,皇后的攻擊範圍是同一行,同一列和對角線.
N個皇后要如何安全排列?
在一個二維數組中,一行一行地看,觀察第 i 行的第 i 個皇后能放在第幾列.也就是說,默認每個皇后不可能出現在同一行.
↑顯約束.
所以,在判斷第t個皇后時,只需判斷是否
a.列衝突:x[t]==x[i] i的範圍是1~t-1
b.對角線衝突:t-i==abs(x[t]-x[i]) i的範圍是1~t-1
↑隱約束
二.代碼實現
分爲三個部分
1.全局變量區
2.約束函數
3.遞歸函數
4.調用遞歸函數
1.全局變量
#include<cmath>
const int M = 20;
int n;//表示n個皇后
int x[M];//例如:x[2]=5表示第2行的皇后在第5列,數組稍寬些會少許多麻煩
int countn=0;//n皇后解的個數
2.約束函數
//形參t表示第t個皇后能否與前0~t-1個皇后不衝突
bool Place(int t)//第t個皇后能否放在第i個位置
{
bool flag = true;
for (int i = 1; i < t; i++)//觀察是否與第1~t-1個皇后衝突
{
//在同一列 或 對角線 上
if (x[t] == x[i] || t - i == fabs(x[t] - x[i]))
{
flag = false;
break;
}
}
return flag;
}
3.遞歸函數
一個形式上很簡潔的遞歸.
尚未遍歷到第n行時,遍歷每一列,查找允許的安全位置
能夠遍歷到第n行,說明得出一個解,打印出來
"數組是從0開始的" 這一點有時候很棘手,不如在設置數組時多設置一單位長度.統一從1開始
void queen(int t) //當前遍歷的第t個皇后
{
if (t > n2)
{
countn++;//表示找到一個解
for (int i = 1; i <= n2; i++)
{
cout << x[i] << "\t";
}
cout << endl;
cout << "------------" << endl;
}
else
{
for (int i = 0; i < n2; i++)//分別判斷n個分支
{
l[t] = i;//※(第一次的bug)若不添加該語句,x[]始終處於剛剛初始化的狀態即memset(x,0,sixeof(x)),程序無法運行出結果
if (Place(t))
{
queen(t + 1);
}
}
}
}
4.main()
cout << "請輸入1~20以內的數作爲皇后個數:";
cin >> n2;//Queen.h定義的變量,也可以像函數那樣被main()調用
if (n2 > 20)
{
cout << "輸入值偏大,請重新輸入:";
cin >> n2;
}
queen(1);
cout << "解的個數: " << countn;
三.拓展優化
回溯法的優化,就是要減小解空間的大小,精簡m叉樹.具體來說,就是將隱約束 變成 顯約束.
在以上代碼中,0~n-1的每一個分支都要判斷到並遞歸,這顯然是極大的浪費.
通過隱約束可知,不可能是同一列,可以設置一個bool live[n]數組用於判斷之前元素佔的是哪一列.
在for()裏嵌一個if()判斷來減少循環次數.
最後效果如圖: