N皇后問題
【題目描述】 N皇后問題
世界歷史上曾經出現一個偉大的羅馬共和時期,處於權力平衡的目的,當時的政治理論家波利比奧斯指出:“事涉每個人的權利,絕不應該讓任何權利大的壓過其它力量,使他人無法立足於平等條件與之抗辯的地步。”這類似著名的N皇后問題,即在NXN格的國際象棋上擺放N個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,請問有多少中擺法,並將每種擺法打印出來。圖1所示即是擺法的一種。
【輸入格式】
輸入一個整數,即N(14>N>3)。
【輸出格式】
輸出所有擺法,每個擺法佔一行
【輸入樣例】
4
【輸出格式】
2413
3142
【輸出說明】
N=4的棋盤輸出的兩種方案即圖2所示
遞歸算法1★
若想遍歷所有擺法而無一遺漏,可以逐行從上至下、從左至右嘗試棋子的擺放。以N=4的棋盤爲例,其遍歷過程如圖3所示。
Tips:棋盤座標上一般會想到用二維數組來表示,如定義二維數組a[8][8],則a[0][0]=1代表棋盤第一行第一列有棋子,a[3][4]=0代表棋盤第4行第5列無棋子……但實際上只用一個一維數組可以解決該問題。
例如,使用a[8]來表示棋盤座標時,假設a[0]=7,即表示第1行第7列有棋子,a[1]=2即表示第2行第2列有棋子,而且這種方法無須再判斷兩皇后是否在同一行。
可以定義一個Try(x,y)函數判斷棋盤(x,y)處是否可以放皇后:
(1)不在同一列
(2)不在對角線上,即有兩棋子座標分別爲(X1,Y1),(X2,Y2),則|X1-X2|!=|Y1-Y2|
源碼://N皇后問題——遞歸算法
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int n,a[21];
void print() //打印棋子
{int i=1;
for(;i<=n;i++)
cout<<a[i];
cout<<" ";
if(i%7==0)
cout<<endl;
}
void printGraph() //圖形化打印佈局
{
system("color F0"); //DOs命令改變背景色和前景色,顏色爲16進制數0~F
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
if(a[i] == j)
cout<<"O";
else
cout<<"▅";
cout<<endl;
}
cout<<endl;
}
int Try(int x,int y) //測試x行y列可否擺放棋子,成功返回1,否則返回0
{
int j=1;
while(j<x) //與數組中已放好的數比較
{
if((a[j] == y) || (abs(x-j) == abs(a[j]-y)))
return 0;
++j; //右移一格繼續嘗試
}
return 1;
}
void place(int x) //遞歸函數
{
int y;
if(x>n) //棋子第n行已擺好,則打印成功方案
{
print();
//printGraph(); //打印圖形化佈局
}
else
{
for(y=1;y<=n;++y) //該行棋子依次從左向右移
if(Try(x,y)) //如果可以擺放
{
a[x]=y; //給a[x]賦值
place(x+1); //繼續下一行的遞歸
}
}
}
int main()
{
cin>>n;
place(1); //從第1行開始遞歸
return 0;
}
該程序中有個用於調試的printGraph()函數,選用它可圖形化顯示佈局方案,如圖所示:
爲了深入理解遞歸調用的過程,設N=4,則可將place(4)到place(4)的過程展開,如圖所示:
運行過程如圖下圖所示。
模擬該程序運行過程時會發現,通過程序不斷的進進退退,最後就打印除了全部的佈局。
由此可知,遞歸中常常隱含回溯,回溯即選擇一條路走下去,發現走不通,就返回去再選一條路走。
以下爲八皇后問題的解決,共92種擺法:
改程序當N>13時,速度將慢的難以忍受,並且考慮到輸出方案浪費了大量的時間,此後的優化算法只需要輸出方案數即可。
遞歸算法2★
以N=4爲例,下圖左是正斜線,右是反斜線,即有2xN-1條、反斜線。
1號正斜線所佔據的棋盤單元爲(1,1)
2號正斜線所佔據的棋盤單元爲(1,2)(2,1)
3號正斜線所佔據的棋盤單元爲(1,3)(2,2)(3,1)
4號正斜線所佔據的棋盤單元爲(1,4)(2,3)(3,2)(4,1)
5號正斜線所佔據的棋盤單元爲(2,4)(3,3)(4,2)
6號正斜線所佔據的棋盤單元爲(3,4)(4,2)
7號正斜線所佔據的棋盤單元爲(4,4)
可以發現規律如下:
(1) 同一正斜線所佔據的棋盤單元行列之和相等,如上面7條正斜線分別爲2、3、4、5、6、7、8.
(2) 同一反斜線所佔據的棋盤單元行列之差相等。
故可以定義bool數組x1[ ]用來記錄行列之和爲i的正斜線是否已經被佔據,bool數組x2[ ]用來記錄行列之差爲i的反斜線是否已經被佔據。考慮到行列之差可能爲負數,棋盤座標(x,y)對應x2[ x-y+n]。
再定義一個bool數組y[ ]判斷列衝突,如果y[i]=1,說明前面已有皇后放到在這一列,再放會發生列衝突,y[i]=0,說明當前第i列還沒有放置皇后。//N皇后——遞歸算法2
#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
int n,num;
int a[20]={0};
bool y[41],x1[41],x2[41];
void place(int x) //遞歸函數
{
if(x>n)
++num; //找到一個擺法,計數器加1
else
{
for(int i=1;i<=n;++i)
{
if(y[i] == 0 && x1[x+i] ==0 && x2[x-i+n] == 0) //如果列,正斜線,反斜線無衝突
{
a[x] = i; //給a[x]賦值
y[i] = 1; //列座標做標記
x1[x+i] = 1; //正斜線做標記
x2[x-i+n] = 1; //反斜線做標記
place(x+1); //繼續下一行的遞歸
y[i] = 0; //恢復
x1[x+i] = 0; //恢復正斜線標記爲0
x2[x-i+n] = 0; //恢復反斜線標記爲0
}
}
}
}
int main()
{
cin>>n;
place(1);
cout<<num<<endl;
}
優化後的程序提高了很多,但是當N>=14時候,速度還是較慢,我們還可以再次基礎上繼續優化,這裏我就不繼續寫了,希望大家能夠自己優化。