一、問題簡介
描述
在n×n 格的棋盤上放置彼此不受攻擊的n 個皇后。按照國際象棋的規則,皇后可以攻擊與之處在同一行或同一列或同一斜線上的棋子。n后問題等價於在n×n格的棋盤上放置n個皇后,任何2 個皇后不放在同一行或同一列或同一斜線上。 設計一個解n 后問題的隊列式分支限界法,計算在n× n個方格上放置彼此不受攻擊的n個皇后的一個放置方案。
Input
輸入數據只占一行,有1 個正整數n,n≤20。
Output
將計算出的彼此不受攻擊的n個皇后的一個放置方案輸出。第1行是n個皇后的放置方案。
Sample Input
5
Sample Output
1 3 5 2 4
二、問題分析
回溯法解的生成
回溯法對任一解的生成,一般都采用逐步擴大解的方式。每前進一步,都試圖在當前部分解的基礎上擴大該部分解。它在問題的狀態空間樹中,從開始結點(根結點)出發,以深度優先搜索整個狀態空間。這個開始結點成為活結點,同時也成為當前的擴展結點。在當前擴展結點處,搜索向縱深方向移至一個新結點。這個新結點成為新的活結點,並成為當前擴展結點。如果在當前擴展結點處不能再向縱深方向移動,則當前擴展結點就成為死結點。此時,應往回移動(回溯)至最近的活結點處,並使這個活結點成為當前擴展結點。回溯法以這種工作方式遞歸地在狀態空間中搜索,直到找到所要求的解或解空間中已無活結點時為止。
回溯法與窮舉法
回溯法與窮舉法有某些聯系,它們都是基於試探的。窮舉法要將一個解的各個部分全部生成后,才檢查是否滿足條件,若不滿足,則直接放棄該完整解,然后再嘗試另一個可能的完整解,它並沒有沿着一個可能的完整解的各個部分逐步回退生成解的過程。而對於回溯法,一個解的各個部分是逐步生成的,當發現當前生成的某部分不滿足約束條件時,就放棄該步所做的工作,退到上一步進行新的嘗試,而不是放棄整個解重來。
解題思路
- 用 d[i]=k 表示第 i 個皇后放在第 k 個位置上,
- 然后從第1個皇后,第1個位置開始,每次放置前先調用 check() 函數判斷與其他皇后是否沖突
- 如果不沖突則放置
- 如果沖突則移至下一個位置,如果位置到了最后一個,則不放,且將上一次放置的皇后移至下一個位置,遞歸調用。
- 直至放置完畢所有皇后(flag=true) 或者 所有位置遍歷結束。
三、代碼
#include <iostream>
#include <stdlib.h>
using namespace std;
int n=0;
int d[21];
bool flag = false;
void backtrack(int);
bool check(int);
int main(){
cin>>n;
backtrack(1);
for(int i=1;i<n;i++)
cout<<d[i]<<' ';
cout<<d[n];
}
void backtrack(int k){
if (k == n+1){
flag=true;
return;
}
for (int i=1;i<=n;i++){
d[k] = i;
if (check(k))
backtrack(k+1);
if (flag)
return;
d[k] = 0;
}
}
bool check(int k){
for (int i=1;i<=k;i++)
for (int j=i+1;j<=k;j++)
if (d[i] == d[j] || abs(d[i] - d[j]) == abs(i-j))
return false;
return true;
}
python版
n = int(input())
d = [0] * (n+1)
flag = 0
def check(k):
if k == 1:
return True
for i in range(1, k+1):
for j in range(i+1, k+1):
# print(j,end=' ')
if d[i] == d[j] or abs(d[i] - d[j]) == abs(i-j):
return False
return True
def dfs(k):
global flag
if k == n+1:
flag=1
return
# print('k =', k)
for i in range(1, n+1):
d[k] = i
# print('d[',k,'] = ',i,sep='')
if check(k):
dfs(k+1)
if flag == 1:
return
d[k] = 0
dfs(1)
for x in d[1:n]:
print(x,end=' ')
print(d[n],end='')
