八皇后問題是由國際西洋棋棋手馬克斯·貝瑟爾于1848年提出的問題,是回溯算法的典型案例。
問題表述為:在8×8格的國際象棋上擺放8個皇后,使其不能互相攻擊,即任意兩個皇后都不能處于同一行、同一列或同一斜線上,問有多少種擺法。
像這樣的棋盤:
Queen
對棋盤行和列標號,可以使用 0~7
或 1~8
,通過行數與列數進行加減計算,得到如下的內容:
Queen_leftLine.png
Queen_rightLine.png
行 - 列
和 行 +列
,可以清晰的看到具有很明顯的規律
行 - 列
,紅線的方向,從左到右,從上到下的斜線,取值范圍 [-7 , 7]
,,共15個元素
-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7
行 +列
,紅線的方向,從左到右,從下到上的斜線,取值范圍 [2 , 16]
或 [0 , 14]
,共15個元素
當列和行標號范圍 "1~8"
2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
當列和行標號范圍 "0~7"
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
解題方法 :
注 :此處使用行列的標號范圍 "1~8"
設置數組,判斷某位置是否處于一個安全的位置
1. 建立數組,并初始化
因為每種斜線最多有15種可能,行列最多只有8種可能
// 判斷左斜線
int r[16] = { 0 };
// 判斷左斜線
int l[16] = { 0 };
// 判斷列
int h[8] = { 0 };
例如: 取 x = 4, y = 5 位置,其左斜線 -1 ,右斜線 9 ,列 4
由于數組初始化為0,當填入一行皇后,需要進行占位,利用行列號來修改數組位置的值,修改為1
左斜線(x-y)取值范圍:[-7 , 7] ,因此在左斜線 x-y+8, 則對應數組 l[1] 到 l[15]
右斜線(x+y)取值范圍:[2 , 16] ,因此在右斜線 x+y-1則對應數組 r[1] 到 r[15]
例如:取 x = 4, y = 5 位置,4-5=-1
,4+5=9
,因此 l[7]=1
、r[8]=1
2. 創建數組保存每種情況 , 并統計數量
// 符合條件的數量
int n = 0;
int que[8] = { 0 };
創建一維數組,來存放每行皇后的位置,每個皇后的取值都不同,也就是取值由0到7
3. 創建函數,輸出保存數組的情況
void Print()
// 輸出
{
cout << "第"<<n << "種:" ;
for (int i = 0; i < 8; ++i)
{
cout << que[i] ;
}
cout << endl;
}
4. 設計遞歸函數
遞歸參數為行數
void Queen(int row = 0)
// 輸入行數
{
// 函數出口
if(row > 7)
{
n++;
Print();
return;
}
for (int i = 0; i < 8; ++i)
{
// 當一行到結尾,也沒有找到解法,結束,返回上一行,
if(i>7)
{
return;
}
// 判斷是否符合條件
if( !l[row-i+8] && !r[row+i-1] && !h[i])
{
// 符合條件保存該行的位置,并標記影響的斜線和列
que[row] = i;
h[i] = 1;
l[row-i+8] = 1;
r[row+i-1]= 1;
// 本行已找到,跳到下一行
Queen(row+1);
// 因為下一行沒有找到位置,結束函數,此行該位置不能得到的結果,因此清除之前設置的內容
que[row] = 0;
l[row-i+8] = 0;
r[row+i-1]= 0;
h[i] = 0;
}
}
}
例子:
#include<iostream>
#include<string>
#include<stdlib.h>
using namespace std;
// 符合條件的數量
int n = 0;
// 數組存放每行皇后的位置
int que[8] = { 0 };
// 判斷左斜線
int l[16] = { 0 };
// 判斷左斜線
int r[16] = { 0 };
// 判斷列
int h[8] = { 0 };
void Print()
// 輸出
{
cout << "第"<<n << "種:" ;
for (int i = 0; i < 8; ++i)
{
cout << que[i] ;
}
cout << endl;
}
void Queen(int row = 0)
// 輸入行數
{
// 每次遞歸結束的出口
if(row > 7)
{
// 完成一次遞歸,結果加一,并打印,結束遞歸
n++;
Print();
return;
}
for (int i = 0; i < 8; ++i)
{
// 當一行到結尾,也沒有找到解法,結束,返回上一行,
if(i>7)
{
return;
}
// 判斷是否符合條件
if( !l[row-i+8] && !r[row+i-1] && !h[i])
{
// 符合條件保存該行的位置,并標記影響的斜線和列
que[row] = i;
h[i] = 1;
l[row-i+8] = 1;
r[row+i-1]= 1;
// 本行已找到,跳到下一行
Queen(row+1);
// 因為下一行沒有找到位置,結束函數,此行該位置不能得到的結果,因此清除之前設置的內容
que[row] = 0;
l[row-i+8] = 0;
r[row+i-1]= 0;
h[i] = 0;
}
}
}
int main(int argv,char* argc[])
{
Queen();
cout << n <<endl;
system("pause");
return 0;
}
N皇后問題
通過八皇后可以推出N皇后的問題的解決方案
主要問題在于斜線的區間和數量
注:行列的標號范圍 "1~n"
當為N皇后,x+y的取值范圍 [2 , 2n]
, x-y 取值范圍 [ 1-n , n-1 ]
, 數量都為 2n-1
#include<iostream>
#include<cstdlib>
using namespace std;
template<int size>
class Queen
{
private:
int num = 0;
// 判斷左斜線
int l[size*2] = { 0 };
// 判斷左斜線
int r[size*2] = { 0 };
// 判斷列
int h[size] = { 0 };
// 數組
int que[size] = { 0 };
public:
Queen(){};
~Queen(){};
public:
void check(int row = 0)
{
if(row >= size)
{
num++;
Prints();
return;
}
for (int i = 0; i < size; ++i)
{
if( !l[row-i+size] && !r[row+i-1] && !h[i])
{
que[row] = i;
h[i] = 1;
l[row-i+size] = 1;
r[row+i-1]= 1;
check(row+1);
// 因為下一行沒有找到,因此此行該位置不能得到應有的結果,因此清空設置的內容
que[row] = 0;
l[row-i+size] = 0;
r[row+i-1]= 0;
h[i] = 0;
}
}
}
void Prints()
// 輸出
{
cout << "第"<<num << "種:" ;
for (int i = 0; i < size; ++i)
{
cout << que[i] ;
}
cout << endl;
}
};
int main(int argc, char const *argv[])
{
Queen<8> Sir;
Sir.check();
system("pause");
return 0;
}