递归–八皇后问题(回溯算法)
问题:
在8x8的国际象棋的棋盘上摆八个皇后,使其不能够互相攻击。即任意两个皇后不能够处在同一行,同一列,或者是同一斜线,问有多少种摆法?92
思路分析
- 第一个皇后放在第一行的第一列
- 第二个皇后从第二行第一列开始放,然后判断可不可以,可以,就放第三个皇后,也从第三行第一列开始放;不可以在换下一个位置,在判定
- 放第三个皇后,还是从第三行的每一列开始放,找到正确位置,开始放第四个。。。。一直放到第八个皇后,然后开始回溯,直到找到所有的解
- 然后第一个皇后再换位置,放到第一行的第二列,再开始上述方法进行回溯,直到放到最后一列。
- 说明:理论上使用二维数组模拟棋盘,但实际上可以通过算法用一个一维数组来解决问题。arr[8] = {1,2,3,4,5,6,7,0}每个数组元素的值代表放第几列,数组元素的顺序对应着放在第几行,如:arr【0】 = 1,表示第一行第二列。
代码实现
package queen;
public class TwoDimension {
//定义一个max表示共有多少个皇后
static int max = 8;
static int[] array = new int[max];
public static void main(String[] args) {
setQueen(0);
}
//根据思路分析建造棋盘--- 放皇后----判定是否冲突---放皇后
//编写方法来放置皇后
private static void setQueen(int hang){
//总共是要放八个皇后,八个循环
if (hang == 8){
print();
System.out.println("到达终点");
//如果是第九个可以放才推出递归,那么要是第八个可以,但是第九个不可以,那不就完蛋了吗
//所以,到了第九个就可以了
return;
//似乎要是全的话,不仅仅是第七行,最后一行,应该是最后一行的后面一行
}
for(int i = 0;i < 8;i ++){
System.out.println("开始递归" + i + "lie" + hang + "hang");
array[hang] = i;
//最外面放置行,这样放置有问题,放第一个,第一个固定,再放第二个,在固定
//他是在上一个固定的基础上再放下一个,我这样就是依次遍历
if(isOk(hang)){
setQueen(hang + 1);
}
//当前的行列要是不成立就再换一个,下一列
//其实我居然看了一下我才会做啊
}
}
//判断哪个是否冲突的方法
private static boolean isOk(int m){
//m表示当前传入的是第几行,n表示为第几列
//遍历判断是否相等吗?
boolean isFlag = true;
for (int i = 0;i < m;i ++){
//i表示为第几行地皇后
if(array[i] == array[m]){
System.out.println("是否同列");
//第一行所在皇后的对应的列是不合法的
isFlag = false;
}else if(i == m){
//第i行是不合法的
isFlag = false;
//关于斜边对应不合法,可以用数学地关系来推导
//如果行数与列数的差相等,那就是相等
//不用像原来一样推导
}else if(Math.abs(i- m)==Math.abs(array[m]- array[i])){
System.out.println("是否同斜线");
//斜边对应的都是不合法的
//只能写对应斜边地第一格
isFlag = false;
}else{
isFlag = true;
}
}
return isFlag;
}
//传入一个参数比较好,并表示为行数,对应的数组坐标值即为对应的列数,传入两个太混乱
//传入一个,就不用判断是否在同一行,因为你每一次都是再上上一行放的,本来就不在同一行
//算法背后是数学在作用,不能死算,要学会找对应的规律
//方法门将皇后的位置打印出来
private static void print(){
for(int i = 0;i < array.length;i ++){
System.out.print(array[i] + " ");
}
System.out.println();
}
}
运行结果:
分析:正常来说,进入了同列判定的语句,说明该位置与之前的棋子同列,故而不能放在那里,换下一列。但是还进行判定,说明,一直走到底。问题在于,只要有一个不符合条件的那就跳出循环。
修改:在对应的判断同列,同斜线语句中加上跳出语句。
二次运行:
结果:
分析:成功了,当不正确的情况会自动回溯的,直到检索到所有的结果
再次修改:增加可能结果的总数
最终代码:
package queen;
public class TwoDimension {
//定义一个max表示共有多少个皇后
static int max = 8;
static int[] array = new int[max];
static int count = 0;
public static void main(String[] args) {
setQueen(0);
System.out.println("总数为" + count);
}
//根据思路分析建造棋盘--- 放皇后----判定是否冲突---放皇后
//编写方法来放置皇后
private static void setQueen(int hang){
//总共是要放八个皇后,八个循环
if (hang == 8){
count ++;
print();
//System.out.println("到达终点");
//如果是第九个可以放才推出递归,那么要是第八个可以,但是第九个不可以,那不就完蛋了吗
//所以,到了第九个就可以了
return;
//似乎要是全的话,不仅仅是第七行,最后一行,应该是最后一行的后面一行
}
for(int i = 0;i < 8;i ++){
//System.out.println("开始递归" + i + "lie" + hang + "hang");
array[hang] = i;
//最外面放置行,这样放置有问题,放第一个,第一个固定,再放第二个,在固定
//他是在上一个固定的基础上再放下一个,我这样就是依次遍历
if(isOk(hang)){
setQueen(hang + 1);
}
//当前的行列要是不成立就再换一个,下一列
//其实我居然看了一下我才会做啊
}
}
//判断哪个是否冲突的方法
private static boolean isOk(int m){
//m表示当前传入的是第几行,n表示为第几列
//遍历判断是否相等吗?
boolean isFlag = true;
for (int i = 0;i < m;i ++){
//i表示为第几行地皇后
if(array[i] == array[m]){
//System.out.println("是否同列");
//第一行所在皇后的对应的列是不合法的
isFlag = false;
break;
}else if(i == m){
//第i行是不合法的
isFlag = false;
break;
//关于斜边对应不合法,可以用数学地关系来推导
//如果行数与列数的差相等,那就是相等
//不用像原来一样推导
}else if(Math.abs(i- m)==Math.abs(array[m]- array[i])){
// System.out.println("是否同斜线");
//斜边对应的都是不合法的
//只能写对应斜边地第一格
isFlag = false;
break;
}else{
isFlag = true;
}
}
return isFlag;
}
//传入一个参数比较好,并表示为行数,对应的数组坐标值即为对应的列数,传入两个太混乱
//传入一个,就不用判断是否在同一行,因为你每一次都是再上上一行放的,本来就不在同一行
//算法背后是数学在作用,不能死算,要学会找对应的规律
//方法门将皇后的位置打印出来
private static void print(){
for(int i = 0;i < array.length;i ++){
System.out.print(array[i] + " ");
}
System.out.println();
}
}
总结分析:
- 根据思路分析开始写方法,放皇后,和判断皇后是否合理两个方法
- 在判定合理的过程总,一旦遇到非法的状况,就跳出,输入下一列,而不是从从头遍历到尾
- 在判定是否为同一斜线的过程中,学会发现一般性的简单的常见的逻辑,而不是把所有情况都列出来
- 在书写放皇后的方法中,先列一列一般不带递归的语句,发现共同逻辑,再根据你所写的语句提炼出递归
- 关于递归,必须具备三要素
- 递归终止条件:在这里是八个棋子放满,容易忽略的是第八个棋子也要正确,故而把终止条件放在第九个棋子
- 递归终止时的处理方法:如果放成功下一步干什么,没放成功下一步有干什么
- 提取重复逻辑:在所列的那么多的一般语句中发现不停的出现两个语句,for和if,
- 确实用一维数组更加方便一点,一维数组有两个量分别是数组下标和当前下标的值,刚好可以表示当前的皇后的位置。而且在循环中还把列给单提出来,确实更加方便
错误补充:
忘记给行赋值对应的列时,就会什么都不出!
错误代码:
运行结果: