1.問題起源->八皇后問題
該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848年提出:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊:即任意兩個皇后都不能處於同一行、同一列或同一斜線上,求解符合條件的解的個數。
2.能否暴力窮舉?
國際象棋的格子數有64個,我們的任務是從中取8個,這是一個排列組合的問題,暴力搜索的次數是:
雖然能夠解決,但是明顯過於笨拙;
3.觀察特徵,減少枚舉總數
我們通過觀察和歸納,可以發現:
如果使得即任意兩個皇后都不能處於同一行、同一列或同一斜線上,那麼恰好每行,每列 都只有一個皇后。
我們只考慮行和列的情況:
在第0行,皇后可擺放的位置有8種;
在第1行,皇后可擺放的位置有7種 ;
在第2行,皇后可擺放的位置有6種;
........
因此,在不考慮對角線的情況下,
枚舉的全集爲:8!=40320 種
較暴力窮舉優異的多,因此我們就從此入手
4.巧用一維數組,簡化數據結構
我們可以用二維數組表示棋盤格,但是會增加數據結構的複雜性,
我們可以充分利用數組的下標和值來建立求解的數據結構。
我們定義一個長度爲8的數組:
其下標代表行的編號;
其值代表列的編號;
這樣相關的比較操作就可以使用一維數組進行
5.算法總體設計
首先,我們需要遍歷棋盤的每一行;
在每一行,我們要嘗試每一列;
每一次列的嘗試之後,都要檢查是否符合條件
如果符合,走下一行(遞歸調用此算法);
如果不符合,走下一列; 如果所有列都不符合,返回上一層遞歸;
爲啥能返回?因爲都執行完了,本層被請出棧了,就到上一層了(上一層的列循環還沒有做完,因此上一層不會被從棧裏面請出去);
如果不符合條件,我們的選擇是返回上一層,而不是走下一次遞歸,這種解法稱爲回溯
5.1 遞歸結構設計
//cur 當前行
public static void que(int cur) {
if(cur==n) {
tot++;
}else {
//i代表當前列
for(int i=0;i<n;i++) {
boolean flag=true;
temp[cur]=i;
//j代表之前已經存在的皇后
for(int j=0;j<cur;j++) {
if(不符合條件) {
flag=false;
break;//看下一列
}
}
if(flag) {
que(cur+1);//符合條件,下一行
}
}
}
}
5.3如何判斷是否發生攻擊
我們外層是按照行來遍歷的,因此不需要檢查是否同行;
我們需要檢查是否同列,是否同對角線:
我們的檢查數組: 其下標代表行的編號; 其值代表列的編號; 不同列,即任意兩個數組元素的值不能相等;
不同對角線,怎麼判斷?
斜率呀!
如果A點座標爲(x1,y1);B點座標爲(x2,y2)
那麼 A與B在對角線的情況就是 |y1-y2|=|x1-x2|
能不能直接用斜率?可以但是分子爲0需要特殊處理,
因此直接用 |y1-y2|=|x1-x2|
6.擴展-N皇后問題(題目來自力扣)
代碼:
class Solution {
static int n;
static int tot=0;
static int[]temp;
public static void que(int cur) {
if(cur==n) {
tot++;
}else {
//i代表當前列
for(int i=0;i<n;i++) {
boolean flag=true;
temp[cur]=i;
//j代表之前的
for(int j=0;j<cur;j++) {
if(temp[cur]==temp[j]||Math.abs(temp[cur]-temp[j])==Math.abs(cur-j)) {
flag=false;
break;
}
}
if(flag) {
que(cur+1);
}
}
}
}
public static void clear() {
n=0;
tot=0;
temp=new int[n];
}
public int totalNQueens(int m) {
if(m<=0){
return 0;
}
if(m==1){
return 1;
}
n=m;
temp=new int[n];
que(0);
int target=tot;
clear();
return target;
}
}
結果: