八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
在这里,我们将会使用二维数组和递归的方法来解决这个复杂的问题。
第一种方法是通过复制最开始的棋盘arr(开始的棋盘是一个二维数组,当然初始化的时候里面默认都为零,我们用1来表示存在皇后)到newarr,然后在newarr棋盘上放置皇后。我们先从第一行的第一列开始存放皇后
接下来,我们判断第二个皇后存放在第二行的第危险,这样会发现正上方以经存在了皇后,所以我们继续判断第二行的第二列是否危险,这样又会发现在对角线的左上方还是存在皇后,我们会继续执行上次的操作,将继续判断第二行的第三列,直到在第二行存放的皇后没有“危险”,然后将皇后放入棋盘对应的位置;当第二行也存放好了,接下来就执行eightqueen(newarr,row+1)函数,即程序走向下一行继续判断然后继续放入皇后,如果按照这种操作一直执行到最后一行,并且最后一行中都不可以存放皇后时,我们就要进行回溯
(最后一行是“危险的”)
此时,我们需要做的就是回溯到上一层,然后将上一层存放的皇后拿掉,接着在上一层再找到存放的合适的位置,如果在上一层还是没有找到,那么就将继续回溯,直到满足条件为止。
上面的图是从第二张图开始回溯得来的,在当前的情况下都没有任何危险,然后就可以接着向下递归了,如果到后面继续存在之前的情况,那我们就进行回溯,然后再递归,直到有一种结果出来为止。
class EightQueen{
public static int count = 0; //创建计数器
public static int[][] arr = new int[8][8]; //创建开始的棋盘数组
public static void main(String[] args){
eightqueen(arr,0); //开始执行递归程序
}
public static void eightqueen(int[][] arr,int row){ //将原来的数组和刚开始的行数传进来
int[][] newarr = new int[8][8]; //创建一个新的8*8的数组
for(int i=0;i<arr.length;i++){ //将之前的arr数组的值赋给newarr
for(int j=0;j<arr[i].length;j++){
newarr[i][j] = arr[i][j];
}
}
if(row == 8){ //递归的终止条件,当row走到第八行的时候说明一种可能以经完成
count++; //记录有多少种情况
show(newarr); //打印当前的这种情况
}
for(int j=0;j<newarr.length;j++){ //遍历新的数组
if(noDangerous(newarr,row,j)){ //判断当前列和对角线上是否有“危险”
for(int c=0;c<newarr.length;c++){ //存放皇后前先将当前行清空(回溯的时候也会将上层清空)
newarr[row][c] = 0;
}
newarr[row][j] = 1; //清空后然后进行皇后的存放
eightqueen(newarr,row+1); //进行函数递归,将新的存放后的素组继续传给下一个函数,然后行数加一
}
}
}
public static void show(int[][] arr){ //打印当前的棋盘
System.out.println("第"+count+"次结果");
for(int i=0;i<arr.length;i++){
for(int j=0;j<arr[i].length;j++){
System.out.print(arr[i][j]+" ");
}
System.out.println();
}
}
public static boolean noDangerous(int[][] newarr,int row,int j){ //判断目前存放的皇后是否存在“危险”,下面的方法很好理解,大家认真阅读就可以了
for(int i=row-1;i>=0;i--){
if(newarr[i][j]==1){
return false;
}
}
for(int x=row-1,y=j-1;x>=0&&y>=0;x--,y--){
if(newarr[x][y]==1){
return false;
}
}
for(int x=row-1,y=j+1;x>=0&&y<newarr.length;x--,y++){
if(newarr[x][y]==1){
return false;
}
}
return true;
}
上述是第一种方法,下面是第二种方法,通过一个二维数组来解决问题,其实两者使用的方法都一样,都是递归和回溯。
class EightQueen1{
public static int[][] arr = new int[8][8];
public static int count = 0;
public static void main(String[] args){
eightqueen(0);
}
public static void show(){ //在打印结果的时候只打印8*8的范围就可以了
System.out.println("第"+count+"次结果:");
for(int i=0;i<arr.length;i++){
for(int j=0;j<arr[i].length;j++){
System.out.print(arr[i][j]+" ");
}
System.out.println();
}
}
public static void eightqueen(int row){ //将行数传进来
if(row == 8){ //当它执行到第八行的时候结束递归
count++;
show();
return ;
}
for(int col=0;col<arr.length;col++){
if(noDangerous(row,col)){ //判断是否“危险”
arr[row][col] = 1; //通过判断,然后将皇后存放进去
eightqueen(row+1); //开始向下一行进行递归
}
//回溯,将上一层的皇后置为零
arr[row][col] = 0;
}
}
public static boolean noDangerous(int row,int col){ //判断函数
for(int i=row-1;i>=0;i--){
if(arr[i][col]==1){
return false;
}
}
for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--){
if(arr[i][j]==1){
return false;
}
}
for(int i=row-1,j=col+1;i>=0&&j<arr.length;i--,j++){
if(arr[i][j]==1){
return false;
}
}
return true;
}
上面的两个方法其实思路都是一样的,在判断危险这个函数中,我们其实可以运用更为简单的方法,利用绝对值来进行简化代码。