文章目录
《八皇后问题》& 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 区域二:一次递归所需要经历的
-
循环语句加判断语句,对当前行每一列进行判断,符合就可以放置
-
对攻击盘,进行备份,以便回溯的时候能够恢复状态
-
放置
-
进入下一行
-
回归,复原
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