深入浅出讲解八皇后&n皇后问题


这次朋友想看,又有事了,来不及细写,解释讲解部分下次更改细说,代码备注挺详细的,看懂应该都没问题

该文章会从简单的程序开始讲解,也会涉及到一些算法(递归、回溯)、数据结构等一些知识,最后的程序是我现在为止想到的最优算法,讲解过程就是问题思考的过程

规则分析

在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
nxn格放n个皇后,称为n皇后问题,这样的限制然问题简单了许多。

第一种方式:

问题分析

用二维数组表示棋盘,如下图,其中1表示能放棋子的位置,0表示不能放棋子,3表示棋子放置的位置
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从图中可看出一行、一列只能且必须放置一颗棋子,这是一个非常重要的点,优化会用到这一点,但行不能放置棋子且没有放置棋子时就可以返回上一颗棋子的放置并更改放置位置了(回溯算法),将棋盘构造出来之后就可以用for循环来实现算法了,最简单的就是用八个for循环,当然这样写出来会发现同一的代码要写八次,所以把for循环写成一个函数,再在函数里调用本身——递归算法,一定要写跳出条件,不用会!

代码如下1.0

package myself_write.die8q;

import java.util.Arrays;

public class EightQueens0 {
    public static int [][] m = new int[8][8];   //棋盘布局
    public static int w=0;                      //能放多少个棋子
    public static int num=0;                    //记录有多少种放发

    static {
        restart();
    }
    
    public static void main(String[] args) {
        int a=0;
        int[][] arr0 = newArrays(m);
        //for (int j = 0 ; j < 8 ; j++){       //遍历y坐标
            for (int i = 0 ; i < 8 ; i++){   //遍历x坐标
                fun(i,0,a,newArrays(arr0)); //放置方法
                a=0;                         //重置棋子

                //System.out.println("第一轮循环次数:  "+ (++fci));
            }
        //}
        System.out.println("共找到" + num + "种放置方法");
    }

    /** 放置棋子  */
    private static void fun(int x,int y,int a, int[][] arr) {

        int[][] arr1 = downChessman(x,y,arr);                //将x y 放置到arr
        a++;
        //System.out.println(a);
        //printM(arr1);
        if ( a == 8) {
            printM(arr1);
            System.out.println();
            num++;
            return;
        }
        for (int j = y ; j < 8 ; j++){                  //遍历y坐标
            for (int i = 0 ; i < 8 ; i++){              //遍历x坐标
                if (arr1[j][i]==1){
                    fun(i,j,a,newArrays(arr1));                             //方向棋子 排除米字
                    //if (++ci > forci) return;
                }
            }
        }
        a--;

      /*  if (a==7) {                                     // 能放8个棋子则打印出来
            System.out.println();
        }*/
    }

    /** 将放了棋子的米字格全设置为 0  放置位置设置为 3
     *  i为 x坐标   j为y坐标  */
    private static int[][] downChessman(int i, int j , int[][] arr) {
        for (int c = 0; c < 8;c++){
            arr[c][i]=0;
            arr[j][c]=0;
        }

        for (int x=i,y=j;;){
            x--;
            y--;
            if (x <0 || y<0) break;
            arr[y][x]=0;
        }

        for (int x=i,y=j;;){
            x++;
            y++;
            if (x > 7 || y > 7) break;
            arr[y][x]=0;
        }

        for (int x=i,y=j;;){
            x--;
            y++;
            if (x < 0 || y > 7) break;
            arr[y][x]=0;
        }

        for (int x=i,y=j;;){
            x++;
            y--;
            if (x > 7 || y < 0) break;
            arr[y][x]=0;
        }
        arr[j][i]=3;

        return arr;
    }

    /**重置数组*/
    public static void  restart(){
        for (int i = 0 ;i < m.length ; i++){            //y
            for (int j = 0; j < m[i].length; j++) {     //x
                m[i][j]=1;
            }
        }
    }

    //打印m数组
    public static void printM(int[][] ma){
        for (int i = 0;i<ma.length;i++){
            System.out.println(Arrays.toString(ma[i]));
        }
    }

    /** 拷贝数组*/
    public static int[][] newArrays(int[][] a){
        int [][] b = new int[8][8];
        for (int j = 0 ; j < 8 ; j++){                  //遍历y坐标
            for (int i = 0 ; i < 8 ; i++){              //遍历x坐标

                b[j][i] = a[j][i];
            }
        }

        return b;
    }

    /** 判断棋盘上不能在放棋子了*/
    public static boolean p(int[][] arr){
        for (int j = 0 ; j < 8 ; j++){                  //遍历y坐标
            for (int i = 0 ; i < 8 ; i++){              //遍历x坐标
                if (arr[j][i] == 1) return true;
            }
        }

        return false;
    }

}

代码如下1.1

这里用到了稀疏数组(不知道算不算,当时就是稀疏数组的思想),排除了旋转对称的情况

package myself_write.die8q;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class EightQueens3 {
    private static int nu = 8;
    private static int[] chessman = new int[nu]; //每种方法棋子的放置位置 只需要记录x的位置,y的位置是数组下表
    private static List<int[]> list = new ArrayList<>();//记录每种方法棋子位置

    public static void main(String[] args) {
        find();
        System.out.println("共找到" + list.size() + "种不考虑旋转的放置方法");
        //list.forEach(arr-> down(arr));
        //System.out.println(Arrays.toString(list.get(0)));

        repeating();
        System.out.println("共找到" + list.size() + "种旋转不重复的放置方法");
        //list.forEach(arr-> down(arr));

    }

    /** 查找所有的放置方法  考虑对称问题*/
    private static void find() {
        int a=0;
        int[][] arr0 =newInt();
        for (int i = 0 ; i < nu ; i++){   //遍历x坐标
            fun(i,(int) 0,a,copyArrays(arr0));                    //放置方法
            a=0;                         //重置棋子
        }
    }

    /** 放置每颗棋子 考虑对称重复  */
    private static void fun(int x,int y,int a, int[][] arr) {
        int[][] arr1 = downChessman(x,y,arr);                //将x y 放置到arr
        chessman[a]=x;                                //记录棋子x位置
        a++;
        if ( a == nu) {
            list.add(Arrays.copyOf(chessman,chessman.length));
            /*printM(arr1);                               //打印棋子在棋盘上的位置
            System.out.println(Arrays.toString(chessman));//打印棋子位置
            System.out.println();*/
            return;
        }
        for (int j = ++y ; j < nu ; j++){                  //遍历剩下的y坐标
            for (int i = 0 ; i < nu ; i++){              //遍历x坐标
                if (arr1[j][i]==1)                      //值为1表示能放下棋子
                    fun(i,j,a,copyArrays(arr1));                             //方向棋子 排除米字
            }
        }
        a--;
    }

    /** 将放了棋子的米字格全设置为 0  放置位置设置为 3  i为 x坐标   j为y坐标  */
    private static int[][] downChessman(int i, int j , int[][] arr) {
        for (int c = 0; c < nu;c++){
            arr[c][i]=0;
            arr[j][c]=0;
        }
        for (int x=i,y=j;;){
            x--;
            y--;
            if (x <0 || y<0) break;
            arr[y][x]=0;
        }
        for (int x=i,y=j;;){
            x++;
            y++;
            if (x > (nu-1) || y > (nu-1)) break;
            arr[y][x]=0;
        }
        for (int x=i,y=j;;){
            x--;
            y++;
            if (x < 0 || y > (nu-1)) break;
            arr[y][x]=0;
        }
        for (int x=i,y=j;;){
            x++;
            y--;
            if (x > (nu-1) || y < 0) break;
            arr[y][x]=0;
        }
        arr[j][i]=3;

        return arr;
    }

    //打印m数组
    private static void printM(int[][] ma){
        for (int i = 0;i<ma.length;i++){
            System.out.println(Arrays.toString(ma[i]));
        }
    }

    /** 拷贝数组*/  //二维数组不能直接用工具类copyOf
    private static int[][] copyArrays(int[][] a){
        int [][] b = new int[a.length][a[0].length];
        for (int j = 0 ; j < a.length ; j++){                  //遍历y坐标
                b[j] = Arrays.copyOf(a[j],a[j].length);
        }

        return b;
    }

    /** 新建棋盘数组*/
    private static int[][] newInt(){
        int[][] arr0 = new int[nu][nu];
        for (int j = 0 ; j < nu ; j++){                  //遍历y坐标
            for (int i = 0 ; i < nu ; i++){              //遍历x坐标
                arr0[j][i] = 1;
            }
        }
        return arr0;
    }

    /** 排除重复*/
    private static void repeating(){
        for (int i=0;i < list.size();i++){
            for (int j = i+1;j < list.size();j++){      //对比第i种放置方法是否和第j种放置方法对称重复
                if(symmetryLift(i,j) || symmetryRight(i,j) || symmetry180(i,j)){
                    list.remove(j);
                    j--;
                }
            }
        }
    }

    /** 左旋转对称判断重复*/
    private static boolean symmetryLift(int i,int j) {
        int[] inti = list.get(i);   //
        int[] intj = list.get(j);
        for (int y = 0; y < nu; y++){   //第i种放置方法的y轴上的棋子
            if( y != intj[(nu-1)-inti[y]]){  //第j种放置方法的第 (nu-1)-xi 个棋子的x坐标是否等于y
                return false;
            }
        }
        return true;
    }

    /** 右旋转对称判断重复*/
    private static boolean symmetryRight(int i,int j) {
        int[] inti = list.get(i);   //
        int[] intj = list.get(j);
        for (int y = 0; y < nu; y++){
            if( (nu-1)-y != intj[inti[y]]){   //j的x的i的yx坐标是是否等于旋转后的x
                return false;
            }
        }
        return true;
    }

    /** 180°旋转对称判断重复*/
    private static boolean symmetry180(int i,int j) {
        int[] inti = list.get(i);   //
        int[] intj = list.get(j);
        for (int y = 0; y < nu; y++){
            if( (nu-1)-inti[y] != intj[(nu-1)-y]){
                return false;
            }
        }
        return true;
    }

    /** 按棋子位置放置棋子*/
    private static void down(int[] arr){
        int[][] arrqizi = newInt();
        for (int i=0;i<arr.length;i++){
            downChessman(arr[i],i,arrqizi);
        }
        printM(arrqizi);
        System.out.println();
    }

}

第二种方式:

第一种方式用的每一种放置方式都要用用到大量的二维数组,低阶的问题没什么,高阶的皇后问题就太浪费内存了,而且代码非常废,如果用简单的数学知识就可以让代码变的非常简介,如下
在这里插入图片描述

代码如下1.2

package myself_write.die8q;
public class QueensFunc {
    private static int queenNuber = 9;
    private static int[] arr = new int[(queenNuber+1)]; //记录棋子摆法位置
    public static void main(String[] args) {
        long t1 = System.currentTimeMillis();
        find();
        System.out.println("计算已完成!共有   "+ arr[0] +"  种放置方法");
        System.out.println(System.currentTimeMillis()-t1 +"毫秒");
    }
    /** 查找所有的放置方法  考虑对称问题*/
    private static void find() {
        long t1 = System.currentTimeMillis();
        int y=1;                           //棋子数量
        for (int i = 1 ; i < arr.length ; i++){   //遍历x坐标
            downChessman(i,y);                    //放置方法
            y=1;                         //重置棋子数量
            System.out.println("计算已完成 : " + ((int)(i*100/(double)queenNuber)) +"%");
            System.out.println("计算用时   : " + (System.currentTimeMillis()-t1) +"毫秒");
            System.out.println();
        }
    }
    /** 放下每颗棋子*/
    private static void downChessman(int x,int y){
        arr[y] = x;  //a表示y坐标   x为x坐标   放下第y颗棋子
        y++;
        if (y==arr.length){
            //num.add(Arrays.copyOf(arr,arr.length));//将符合规程的摆放方法记录到num中
            arr[0]++;
            return;
        }
        for (int i = 1 ; i < arr.length ; i++){   //遍历x坐标 1到8个坐标是否能方向棋子
            if (yesOrNo(i,y)) {                                  //判断第y+1颗棋子能不能放下
                downChessman(i, y);                    //放置方法
            }
        }
        y--;                         //重置棋子
    }
    /** 判断该位置是否能放下一颗棋子*/
    private static boolean yesOrNo(int x,int y) {
        for (int i = 1; i < y; i++) {                                 //第i颗棋子,y坐标
            if (x == arr[i] || (x-arr[i]) == (y - i) || (x-arr[i]) == (i - y)) return false;  //与之前放的棋子放置冲突就跳出
        }
        return true;
    }
}

代码如下1.3

package myself_write.die8q;
public class QueensFuncation {
    private static byte queenNuber = 16;
    private static byte[] arr = new byte[queenNuber]; //记录棋子摆法位置
    private static long nuber=0;
    public static void main(String[] args) {
        long t1 = System.currentTimeMillis();
        find();
        System.out.println("计算已完成!共有   "+ nuber +"  种放置方法");
        System.out.println(System.currentTimeMillis()-t1 +"毫秒");
    }
    /** 查找所有的放置方法  考虑对称问题*/
    private static void find() {
        byte y=0;                                  //棋子数量
        for (byte i = 0 ; i < arr.length ; i++){   //遍历x坐标
            downChessman(i,y);                    //放置方法
            y=0;                                    //重置棋子数量
        }
    }
    /** 放下每颗棋子*/
    private static void downChessman(byte x,byte y){
        arr[y] = x;  //y表示y坐标   x为x坐标   放下第y颗棋子
        y++;
        if (y == arr.length){
            nuber++;
            return;
        }
        for (byte i = 0 ; i < arr.length ; i++){   //遍历x坐标 0到7个坐标是否能方向棋子
            if (yesOrNo(i,y)) {                                  //判断第y+1颗棋子能不能放下
                downChessman(i, y);                    //放置方法
            }
        }
        y--;                         //重置棋子
    }
    /** 判断该位置是否能放下一颗棋子*/
    private static boolean yesOrNo(byte x,byte y) {
        for (byte i = 0; i < y; i++) {                                 //第i颗棋子,y坐标
            if (x == arr[i] || (x-arr[i]) == (y - i) || (x-arr[i]) == (i - y)) return false;  //与之前放的棋子放置冲突就跳出
        }
        return true;
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值