八皇后問題、如果在8×8的象棋棋盤上,放上8個皇后,使得每個皇后不在同一行,同一列,同一斜線上,試輸出所有可能的擺放方法。
顯而易見的,用深搜回溯法解決,每一列只能放下一枚皇后棋子,那麼用一個一維數組記錄皇后的位置,然後開始下一列(如果列數小於8),在列數小於8的情況下如果找不到滿足條件的就進行回朔,否則進行輸出。代碼如下:
#include<stdio.h>
int place[8];//用於記錄棋子在棋盤上位置的一維數組,設定爲全局變量,便於後面的函數利用
bool judge(int x){
for(int i=0;i<x;i++)
if((x==i)||(place[i]==place[x])||(x-i==place[x]-place[i])||(x-i==place[i]-place[x]))
return false;
return true;
}//判斷位置是否符合條件的函數
void method(int m){
while(m!=-1){
place[m]++;
while(!judge(m))
place[m]++;//不斷往下挪,直到找到符合條件的下一個位置(不限定在棋盤內)
if(place[m]>8){
place[m--]=0;//如果不在棋盤內,即找不到符合條件的就進行回溯
}
else if(m==7){
for(int i=0;i<8;i++)
printf("%d ",place[i]);
printf("\n"); //如果已經找到最後一列,就進行輸出,同時m不變
}
else
m++;//如果不是最後一列,就進行下一列
}
}
int main(){
for(int i=0;i<8;i++)
place[i]=0;//初試化位置全部設爲0,而實際可能的位置從1開始
method(0);
return 0;
}
實際上這道題如果用遞歸的思路解決的話會更方便更直接更好理解,代碼如下
#include<stdio.h>
int queen[8];//記住一定要將這個數組當做全局變量來處理,這樣的話在函數中處理的時候會更加的方便
int putqueen(int line, int depth)
{
int i;
for(i=0; i<depth; i++)
if(queen[i]== line)
return 0;
for(i=0; i<depth; i++)
if((depth-i)==(queen[i]>line?queen[i]-line:line-queen[i]))
return 0;
return 1;
} // 用於判斷單個的落子是否滿足八皇后規則的函數
void search(int depth)
{
int s=0, i, j;
if(depth>=8)
{
for(j=0; j<8; j++)
printf("%d ", queen[j]+1);//因爲開始的時候用的是0來計數,所以所有的數字都被加上了1
printf("\n");
}
for(i=0; i<8; i++)
if(putqueen(i, depth))
{
queen[depth] = i;
search(depth+1); //進行遞歸
}
}
int main()
{
search(0);
return 0;
}
運行兩個程序可以發現,不僅僅兩個程序都是正確的,甚至連輸出結果的順序都是一模一樣的,其實兩個程序類似的是下一次的輸出都是相對於上一次輸出儘可能保留的結果。
那麼再看跳馬問題:
跳馬問題、在一個5×5的棋盤中,自左上角位置開始,由左至右,由上至下給25個方格以此編號,馬位於1號位置,也就是左上角的位置。試輸出所有的路徑方式,使得該路徑下馬按照走日的方法遍歷整個棋盤且不重複。
首先類似的一點思路是跳馬問題中的回溯是比較困難的,當然也可以寫,因爲在八皇后問題中回溯只要往後調一列,然後往下選一個就可以了,而在跳馬問題中回溯首先你要知道原來走子的方向是哪裏,再往排序後的方向移動,那麼我們需要明確一點,我們依舊需要對不同的走法進行排序。
#include<stdio.h>
#include<stdlib.h>
int routing[25];//用於記錄棋盤中的訪問路徑
int Track[5][5];//用於記錄棋盤中哪些位置被訪問過,哪些位置沒有被訪問過
int variety=0;//用於記錄不同路徑的種類數
bool InChess(int x,int y){
return x>-1&&x<5&&y>-1&&y<5;
} //用於判斷棋子的位置是在棋盤上還是在棋盤之外
int Trans(int x,int y){
return 5*y+x+1;
}//將座標形式的棋子位置轉換爲1到25的形式
bool next(int &x,int &y,int flag){
switch(flag)
{
case 0:
if(InChess(x+1,y+2)&&!Track[x+1][y+2]){
x++;
y+=2;
return true;
}
break;
case 1:
if(InChess(x+1,y-2)&&!Track[x+1][y-2]){
x++;
y-=2;
return true;
}
break;
case 2:
if(InChess(x+2,y-1)&&!Track[x+2][y-1]){
x+=2;
y--;
return true;
}
break;
case 3:
if(InChess(x+2,y+1)&&!Track[x+2][y+1]){
x+=2;
y++;
return true;
}
break;
case 4:
if(InChess(x-1,y+2)&&!Track[x-1][y+2]){
x--;
y+=2;
return true;
}
break;
case 5:
if(InChess(x-1,y-2)&&!Track[x-1][y-2]){
x--;
y-=2;
return true;
}
break;
case 6:
if(InChess(x-2,y-1)&&!Track[x-2][y-1]){
x-=2;
y--;
return true;
}
break;
case 7:
if(InChess(x-2,y+1)&&!Track[x-2][y+1]){
x-=2;
y++;
return true;
}
break;
}
return false;
}//將不同的走子方式進行分類,實際上也是一種排序
void search(int x,int y,int step){//遞歸函數
int x1=x,y1=y;
Track[x][y]=1;//每次進入遞歸函數就將當前的x,y寫入路徑,並標誌爲已佔
routing[step]=Trans(x,y);
if(step==24){
variety++;//出現新路徑
for(int i=0;i<25;i++)
printf("%2d ",routing[i]);//輸出該路徑
printf("\n");
}
for(int i=0;i<8;i++){
x=x1;
y=y1;//因爲在next函數中的x和y發生了變化,因此每次要對x,y進行復原
if(next(x,y,i)){
search(x,y,step+1);//如果找到了符合條件的走法,進行下一輪遞歸
Track[x][y]=0;
routing[step+1]=0;//在每次遞歸之後,要將兩個數組復原
}
}
}
int main(){
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
Track[i][j]=0;
for(int i=0;i<25;i++)
routing[i]=0;
search(0,0,0);
printf(" variety = %d",variety);
return 0;
}