八皇后问题、如果在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;
}