八皇后問題——遞迴+回溯法,皇后遞迴回溯法
八皇后問題,是一個古老而著名的問題,是回溯演算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848年提出:在8X8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。 高斯認為有76種方案。1854年在柏林的象棋雜誌上不同的作者發表了40種不同的解,後來有人用圖論的方法解出92種結果。
求解過程:
採用遍曆的辦法,就是採用將每種情況都驗證的辦法最終找出問題的解,但是蠻力遍曆的話,需要遍曆的資料量太大,計算時間花費太大,所以在遍曆的過程中使用回溯法去掉許多不可能的分支,使問題的規模減小許多。
一個可行解可以這樣表示,用一個數組pos[N](N表示皇后的個數,八皇后即為8)表示每一行的皇后應該放在第幾列。從將第一個棋子放在第1行的第1列開始一直遍曆完這個棋子固定在這個位置的所有解,然後再將第一行的棋子固定在第一行的第二列,再次遍曆完所有的解,直到第一行的棋子放在最後的一列,再遍曆完,那麼所有的解就都找出來了。因為程式碼中已經注釋非常詳細,所以這裡不再重複注釋了。程式碼如下:
#include <iostream>#include <bitset>using namespace std;#define N 4 /*設定棋盤寬度*/char pos[N]; /*每一行的這一個棋子放置的位置:0~7*/bitset<N> stat[N]; /*每一行的空閑位置(除去被行列對角線衝突的位置)*/bitset<N> mask[N][N]; /*儲存回溯過程中以前的狀態,因為在對每一行回溯時當時的狀態都不一樣 所以這個單元的大小是stat的N倍,以便儲存N行各自的初始狀態*/int g_count; /*統計有多少種解法*/void print() /*列印出當前的可行解*/{ int i, j; cout<<endl; for(i=0; i < N; i++){ for(j=0; j < pos[i]; j++) cout<<" - "; cout<<" $ "; for(j=pos[i]+1; j < N; j++) cout<<" - "; cout<<endl; }}void queen(int n) /*皇后問題求解函數*/{ int i, j; if(~stat[n] == 0) /*如果這一行沒有空閑位置,這個分支求解失敗*/return; for(i=0; i < N; i++){ if(!stat[n].test(i)) /*test(i)測試第i個bit是否為1,為1返回ture*/{ pos[n] = i; if(n+1 == N) /*找到一個解*/{ print(); g_count++; return; } for(j=n+1; j < N; j++){ mask[n][j] = stat[j]; /*進行新的遍曆前儲存當前未探測行的空閑狀態*//*從j=n+1開始儲存,前面j=0~n的位置已經儲存過了,再儲存意義也不大*/ stat[j].set(i); /*縱向標記非空閑位置*/ if(i+j-n < N) stat[j].set(i+j-n); /*正對角線方向標記非空閑位置*//*正對角線直線方程:I=kN+b(k=1)->i=n+b->b=i-n==>i-n+j即表示新的I值*/ if(i+n-j >= 0) stat[j].set(i+n-j); /*反對角線方向標記非空閑位置*//*反對角線直線方程:I=kN+b(k=-1)->i=-n+b->b=i+n==>-(j)+i+n即表示新的I值*/ } queen(n+1); /*本行探測完畢,進行下一行的探測*/ for(j=n+1; j < N; j++) stat[j] &= mask[n][j]; /*探測失敗,回退(返回上一次的狀態)*/ } }}int main(int argc, char* argv[]){ int i,j; g_count=0; for(i=0;i<N;i++) stat[i].reset(); for(i=0; i < N; i++) for(j=0; j < N; j++) mask[i][j].reset(); cout<<"result is:"; queen(0); cout<<endl<<"總共有"<<g_count<<"種排法."<<endl<<endl;system("pause"); return 0;}
程式運行結果:
一個程式:用遞迴演算法(回溯法)做八皇后問題?
這個應該不錯
var a:array[0..11] of integer;
n,k,t:integer;
procedure p(k:integer);
var i,j:integer;
g:boolean;
begin
if k>n then begin
for i:=1 to n-1 do
write(a[i],' ');
writeln(a[n]);
t:=t+1;
end
else begin
for i:=1 to n do begin
g:=true;
for j:=1 to k-1 do
if (i=a[j]) or (abs(i-a[j])=abs(k-j)) then g:=false;
if g then
begin
a[k]:=i;
p(k+1);
end;
end;
end;
end;
begin
readln(n);
t:=0;
k:=1;
p(k);
if t=0 then writeln('no solute!');
if t>0 then writeln(t);
end.
C++語言版用回溯法解決八皇后問題的代碼
解析:遞迴實現n皇后問題。
演算法分析:
數組a、b、c分別用來標記衝突,a數組代表列衝突,從a[0]~a[7]代表第0列到第7列。如果某列上已經有皇后,則為1,否則為0。
數組b代表主對角線衝突,為b[i-j+7],即從b[0]~b[14]。如果某條主對角線上已經有皇后,則為1,否則為0。
數組c代表從對角線衝突,為c[i+j],即從c[0]~c[14]。如果某條從對角線上已經有皇后,則為1,否則為0。
代碼如下:
#include <stdio.h>
static char Queen[8][8];
static int a[8];
static int b[15];
static int c[15];
static int iQueenNum=0; //記錄總的棋盤狀態數
void qu(int i); //參數i代表行
int main()
{
int iLine,iColumn;
//棋盤初始化,空格為*,放置皇后的地方為@
for(iLine=0;iLine<8;iLine++)
{
a[iLine]=0; //列標記初始化,表示無列衝突
for(iColumn=0;iColumn<8;iColumn++)
Queen[iLine][iColumn]='*';
}
//主、從對角線標記初始化,表示沒有衝突
for(iLine=0;iLine<15;iLine++)
b[iLine]=c[iLine]=0;
qu(0);
return 0;
}
void qu(int i)
{
int iColumn;
for(iColumn=0;iColumn<8;iColumn++)
{
if(a[iColumn]==0&&b[i-iColumn+7]==0&&c[i+iColumn]==0)
//如果無衝突
{
Queen[i][iColumn]='@'; //放皇后
a[iColumn]=1; //標記,下一次該列上不能放皇后
b[i-iColumn+7]=1; //標記,下一次該主對角線上不能放皇后
c[i+iColumn]=1; //標記,下一次該從對角線上不能放皇后
if(i<7) qu(i+1); //如果行還沒有遍曆完,進入下一行
else //否則輸出
{
//輸出棋盤狀態
int iLine,iColumn;
printf("第%d種狀態為:\n",++iQueenNum);
for(iLine=0;iLine<8;iLine++)
{
for(iColumn=0;iColumn<8;iColumn++)
printf("%c ",Queen[iLine][iColumn]);
printf("\......餘下全文>>