《八皇后问题》Java数据结构-JavaProject-JavaOJ题集

《八皇后问题》& Java数据结构 & JavaProject & JavaOJ题集

  • n皇后的力扣链接
    • 这里我优化了展现形式,放在oj上要去掉
  • N叉树问题(二叉树扩展)
    • 技巧:画图分析---->判断是哪种数据结构
  • 打印每组解

1. 八皇后问题定义 & 知识背景

八皇后问题英文Eight queens),是由国际象棋棋手马克斯·贝瑟尔于1848年提出的问题,是回溯算法的典型案例。

问题表述为:在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。如果经过±90度、±180度旋转,和对角线对称变换的摆法看成一类,共有42类。计算机发明后,有多种计算机语言可以编程解决此问题。

  • 后来衍生为《n皇后问题》,即n*n棋盘能放n个皇后

  • 回溯算法即—时间倒流

    • 可用递归实现(本章重点)
    • 可用Stack栈实现
    • 我们想象一个场景:如果我们每一次走错都能回溯上一步,那么不就可以解决问题了。
      • 待我们走完,不就可以记录下来次数了吗?
  • 重要思想:

    • 每一行只能有一个Queen,那么我们就可以每一行各选一个进行判断
    • 八个攻击方向以及组合起来的攻击范围的判断
      • 即一个棋盘的元素多个属性,一般不会用个类代表棋盘元素,而是用 【双生棋盘】
      • 我们可以创建两个棋盘 —> 左盘记录攻击范围,右盘记录实时Queen位置

在这里插入图片描述

在这里插入图片描述

  • 就拿简单的四皇后来看,棋盘每一行选一个位置放皇后,遇到不能放的就回溯
  • 如图可知,四叉树数据结构—>递归实现
    • 重点在于如何设计递归----并不是每一次都进入四次递归(并且是N叉树问题),可能0次or1次······

2. 棋盘设计

  • 在以上理论基础下,我们开始设计吧!

  • 这里我用的是二维顺序表,看起来麻烦了点,但也是对这一数据结构的练习!

    • 完全可以用二维数组代替!
  •         List<List<Character>> queenBoard = new ArrayList<>();
            List<List<Character>> attack = new ArrayList<>();
    
        //初始化
        for(int i = 0; i < N; i++) {
            queenBoard.add(
                 new ArrayList<Character>() {
				//区域一
                 }
            );
        }
        for(int i = 0; i < N; i++) {
            attack.add(
                    new ArrayList<Character>() {
				//区域二
                    }
            );
        }
                	//区域1
				{
                    for(int i = 0; i < N; i++) {
                        add(' ');
                    }
                }
                	//区域2
				{
                    for(int i = 0; i < N; i++) {
                        add(' ');
                    }
                }
  • 这里我用到一个技巧—> ArrayList<类型不能省略>() + { 语句 }; 的组合,

    • 注意:语句应该也被代码块包裹!
  • 可以在这个对象没名字的时候,就实现调用其中的方法,并且外界变量并不会被其捕获,并且this就是这个匿名对象

    • 本质上,就是匿名内部类的内置(实例代码块),在构造方法调用前就已经调用完毕。
    • 可想而知,匿名内部类功能很大!不仅能重写,还能【添加文本】!
  • 但是,实例化一个有名字对象也就够了!这里是为了拓展知识点!
    在这里插入图片描述

  • 以上就是初始化效果!(左边皇后盘,右边攻击盘)

  • 以下是打印技巧

  •         for(int i = 0; i < queenBoard.size(); i++) {
                System.out.print(queenBoard.get(i));
                System.out.print("         ");
                System.out.println(attack.get(i));
            }
    

3. 放置皇后

public static final int N = 4; //N皇后的【N常量】
  • 以四皇后为例子!
  • 主要是要让attack棋盘的数值发生变化!
public static void putOn(List<List<Character>> qB,
                         List<List<Character>> aT
                        , int row, int col) {
    //以下两步是必然发生的
    qB.get(row).set(col, 'Q');
    aT.get(row).set(col, '1');
    //技巧:方向数组
    //区域1
    
    //攻击
    //区域2
    for (int i = 1; i <= aT.size(); i++) {//保证覆盖整个整个棋盘
        for (int j = 0; j < 8; j++) {
            int iX = col + i * pointX[j];
            int iY = row + i * pointY[j];
            if(iX < aT.size() && iX >= 0 && iY < aT.size() && iY >= 0) {
                aT.get(iY).set(iX, '1');
            }
        }
    }
}

3.1 区域1: 方向数组!

  • 这里两个数组对应下标必须匹配好~
    • 这样就可以通过遍历,就可以实现(i - 1, i + 1) … (i + 1, i - 1) …(i + 2, i - 1)…,等坐标的表示
    • 这里,Y对应行,X对应列
      • 不过这里没关系,反正是中心对称的攻击范围
    //方向数组:
    final int[] pointX = {-1, 1, 0, 1, -1, 0, 1, -1};
    final int[] pointY = {0, 0, 1, 1, 1, -1, -1, -1};

3.2 区域2:对attack盘进行标记

  • 其实本身皇后的攻击范围就是这么大,只不过棋盘就这么大
  • 另一方面,这样写可读性高,减少很多判断语句
    • 可以写递归,也可以写多重判断,这里我选择以下方法
  • 加上刚才的方向数组,大大增加可读性!
for (int i = 1; i <= aT.size(); i++) {
    //保证覆盖整个整个棋盘
    for (int j = 0; j < 8; j++) {
        int iX = col + i * pointX[j];
        int iY = row + i * pointY[j];
        //棋盘内即可改为‘1’
        if(iX < aT.size() && iX >= 0 && iY < aT.size() && iY >= 0) {
            aT.get(iY).set(iX, '1');
        }
    }
}

3.3 测试

  • 这里我提供里一个正解来测试,四条语句依次取消注释进行
        putOn(queenBoard, attack, 0, 1);
        putOn(queenBoard, attack, 1, 3);
        putOn(queenBoard, attack, 2, 0);
        putOn(queenBoard, attack, 3, 2);
        for(int i = 0; i < queenBoard.size(); i++) {
            System.out.print(queenBoard.get(i));
            System.out.print("         ");
            System.out.println(attack.get(i));
        }

在这里插入图片描述

  • 我改以下数据,扩大棋盘,效果更明显:

  • public static final int N = 8;

  • 		putOn(queenBoard, attack, 4, 4);
            for(int i = 0; i < queenBoard.size(); i++) {
                System.out.print(queenBoard.get(i));
                System.out.print("         ");
                System.out.println(attack.get(i));
            }
    

在这里插入图片描述

4. 递归设计

  • public static void research(List<List<Character>> qB,
                             List<List<Character>> aT
                            ,  int row) {
        //区域一:递归出口1
        
        //区域二:
    }
    
  • 两个棋盘是必须的,存放数值嘛

  • 重点参数row,只要能达到 N,就说明N个皇后已经放好!

    • row代表行,一行一行的测
    • 测到第N行,(为棋盘外)
  • public static List<List<List<Character>>> history = new ArrayList<>();

    • 全局性质的一个记录顺序表(三维的)

4.1 区域一:成功的递归出口

        if(row == N) {
            List<List<Character>> tmp = new ArrayList<>();
            for (List<Character> list : qB) {
                tmp.add(new ArrayList<>(list));
            }
            history.add(tmp);
            return;
        }
  • 这个递归出口是正解的递归出口
4.1.1 深拷贝
  • 与二维数组一样,二维顺序表的拷贝也有讲究
    • 这里我用的是顺序表的构造方法来拷贝
  • List<Character>,拷贝每一个这类型的引用,指向同一个对象
    • 而我们应该拷贝对象的对象!
  • 也就是如下代码的含义
        List<List<Character>> tmp = new ArrayList<>();
        for (List<Character> list : qB) {
            tmp.add(new ArrayList<>(list));
        }

4.2 区域二:一次递归所需要经历的

  1. 循环语句加判断语句,对当前行每一列进行判断,符合就可以放置

  2. 攻击盘,进行备份,以便回溯的时候能够恢复状态

  3. 放置

  4. 进入下一行

  5. 回归,复原

for (int i = 0; i < N ;i++) {
    if (aT.get(row).get(i) == '0' && row < N) {
        //浅拷贝了
        // List<List<Character>> tmp = new ArrayList<>(aT);//备份
        
        //深拷贝备份
        List<List<Character>> tmp = new ArrayList<>();
        for (List<Character> list : aT) {
            tmp.add(new ArrayList<>(list));
        }
        
        //放置
        putOn(qB, aT,row, i);
        
        //进入下一行
        research(qB, aT, row + 1);
        
        //回归,复原
        aT = tmp;
        qB.get(row).set(i, ' ');
    }
}

4.3 测试

  • 这里我在放置以及复原时就打印一次,用的也是刚才的打印方法

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 就这样继续下去~ ~ ~

5. 大测试

        research(queenBoard, attack,0);
        for (int i = 0; i < history.size(); i++) {
            System.out.println("第" + (i + 1) + "解:");
            for (int j = 0; j < N; j++) {
                System.out.println(history.get(i).get(j));
            }
        }

在这里插入图片描述

  • 改以下数值: public static final int N = 8;

在这里插入图片描述

  • 测试成功
  • 接下来是力扣,需要修改哦!
  • List<String>,做一个可以将我们的二维顺序表转化为这个类型的方法(空格变为 . )
    • 原本是char[] 类型有优势,因为可以直接构造字符串【我到这里很后悔!对不起xdm】
    • 虽然有toArray方法,但是不能是基本数据类型的数组!
public static List<String> change(List<List<Character>> queenBoard) {
    List<String> list = new ArrayList<>();
    for (int i = 0; i < N; i++) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int j = 0; j < N; j++) {
            stringBuilder.append(queenBoard.get(i).get(j));
        }
        list.add(stringBuilder.toString());
    }
    return list;
}
  • 下面是提交力扣代码
  • 不好意思,我没考虑到这个问题,导致转化花了特别多时间,所有代码运行速率慢
  • 可以再用char[ ] [ ] 数组做一遍哦,效率更快!思路一致
class Solution {
    public List<List<String>> solveNQueens(int n) {
        N = n;
        List<List<Character>> queenBoard = new ArrayList<>();
        List<List<Character>> attack = new ArrayList<>();
        //初始化
        for(int i = 0; i < N; i++) {
            queenBoard.add(
                 new ArrayList<Character>() {
                    {
                        for(int i = 0; i < N; i++) {
                            add('.');
                        }
                    }
                }
            );
        }
        for(int i = 0; i < N; i++) {
            attack.add(
                    new ArrayList<Character>() {
                        {
                            for(int i = 0; i < N; i++) {
                                add('0');
                            }
                        }
                    }
            );
        }
        research(queenBoard, attack,0);
        return history;
    }
     public int N = 8;
    public List<List<String>> history = new ArrayList<>();

    public  void putOn(List<List<Character>> qB,
                             List<List<Character>> aT
                            , int row, int col) {
        qB.get(row).set(col, 'Q');
        aT.get(row).set(col, '1');
        final int[] pointX = {-1, 1, 0, 1, -1, 0, 1, -1};
        final int[] pointY = {0, 0, 1, 1, 1, -1, -1, -1};
        for (int i = 1; i <= aT.size(); i++) {
            for (int j = 0; j < 8; j++) {
                int iX = col + i * pointX[j];
                int iY = row + i * pointY[j];
                if(iX < aT.size() && iX >= 0 && iY < aT.size() && iY >= 0) {
                    aT.get(iY).set(iX, '1');
                }
            }
        }
    }
    public void research(List<List<Character>> qB,
                             List<List<Character>> aT
                            ,  int row) {

        if(row == N) {
            List<String> ret = change(qB);
            history.add(ret);
            return;
        }
        for (int i = 0; i < N ;i++) {
            if (aT.get(row).get(i) == '0' && row < N) {
                List<List<Character>> tmp = new ArrayList<>();
                for (List<Character> list : aT) {
                    tmp.add(new ArrayList<>(list));
                }
                putOn(qB, aT,row, i);
                research(qB, aT, row + 1);
                aT = tmp;
                qB.get(row).set(i, '.');
            }
        }
    }
    public List<String> change(List<List<Character>> queenBoard) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < N; i++) {
            StringBuilder stringBuilder = new StringBuilder();
            for (int j = 0; j < N; j++) {
                stringBuilder.append(queenBoard.get(i).get(j));
            }
            list.add(stringBuilder.toString());
        }
        return list;
    }

}

在这里插入图片描述


文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭🦆

这是我的代码仓库!(在马拉圈的23.2里)代码仓库
八皇后问题 · 具体代码位置

邮箱:2040484356@qq.com

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

s:103

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值