回溯法解決N皇后問題
阿新 • • 發佈:2018-11-14
八皇后問題
在棋盤上放置8個皇后,使得它們互不攻擊,此時每個皇后的攻擊範圍為同行同列和同對角線,要求找出所有解。
遞迴函式將不再遞迴呼叫它自身,而是返回上一層呼叫,這種現象稱為回溯(backtracking)。
當把問題分成若干步驟並遞迴求解時,如果當前步驟沒有合法選擇,則函式將返回上一級遞迴呼叫,這種現象稱為回溯。 正是因為這個原因,遞迴列舉演算法常被稱為回溯法,應用十分普遍。
生成-測試法(低效)
// n皇后問題:生成-測試法
// Rujia Liu
#include<cstdio>
using namespace std;
// C索引表示行,值表示列
// tot表示解的個數
// nc表示執行遞迴函式次數
int C[50], tot = 0, n = 8, nc = 0;
void search(int cur) {
int i, j;
nc++;
if(cur == n) {
for(i = 0; i < n; i++)
for(j = i+1; j < n; j++)
if(C[i] == C[j] || i-C[i] == j-C[j] || i+C[i] == j+C[j]) return;
tot++;
// 輸出正確結果
for(int m=0; m<n; m++) {
printf ("%d", C[m]);
}
printf("\n");
} else for(i = 0; i < n; i++) {
C[cur] = i;
search(cur+1);
}
}
int main() {
scanf("%d", &n);
search(0);
printf("%d\n", tot);
printf("%d\n", nc);
return 0;
}
C[i] == C[j] || i-C[i] == j-C[j] || i+C[i] == j+C[j]
只比較列、主對角線、副對角線,原理見下圖:
輸入輸出:
普通回溯法(高效)
// n皇后問題:普通回溯法
// Rujia Liu
#include<cstdio>
using namespace std;
int C[50], tot = 0, n = 8, nc = 0;
void search(int cur) {
int i, j;
nc++;
if(cur == n) { //遞迴邊界。 只要走到了這裡,所有皇后必然不衝突
tot++;
} else for(i = 0; i < n; i++) {
int ok = 1;
//嘗試把第cur行的皇后放在第i列
C[cur] = i;
//檢查是否和前面的皇后衝突
for(j = 0; j < cur; j++)
if(C[cur] == C[j] || cur-C[cur] == j-C[j] || cur+C[cur] == j+C[j]) {
ok = 0;
break;
}
//如果合法,則繼續遞迴
if(ok) search(cur+1);
}
}
int main() {
scanf("%d", &n);
search(0);
printf("%d\n", tot);
printf("%d\n", nc);
return 0;
}
輸入輸出
4皇后執行了17次,比生成-測試法的341次,少了不少。
優化回溯法,用二維陣列判斷
// n皇后問題:優化的回溯法
// Rujia Liu
#include<cstdio>
#include<cstring>
using namespace std;
// vis表示列、主對角線、副對角線
int C[50], vis[3][50], tot = 0, n = 8, nc = 0;
void search(int cur) {
int i, j;
nc++;
if(cur == n) {
tot++;
} else for(i = 0; i < n; i++) {
//利用二維陣列直接判斷
if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n]) {
//如果不用列印解,整個C陣列都可以省略
C[cur] = i;
//修改全域性變數
vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = 1;
search(cur+1);
//切記!一定要改回來
vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = 0;
}
}
}
int main() {
scanf("%d", &n);
memset(vis, 0, sizeof(vis));
search(0);
printf("%d\n", tot);
printf("%d\n", nc);
return 0;
}
如果在回溯法中使用了輔助的全域性變數,一定要及時把他們恢復原狀。特別的,如果函式有多個出口,則需要在每個出口初恢復被修改的值。