文章内容全部写在了代码注释中
/**
* 八皇后算法:回溯算法
*
* 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
* 回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。
* 但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
* 许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称
*
* 代码思路:
* 采取每行遍历的形式寻找解决方案(成功摆放皇后的方案),找到位置之后表位1,标记列和对角线为1(这些位置都不能存放皇后,默认为0,代表可以放置皇后)
* 找到一种解决方案后输出结果。
* 然后回溯到最后一个摆放皇后一个成功摆放皇后的位置,置为0(不放皇后),并标记列和对角线为0(这些位置可以存放),继续去寻找下一个可以摆放皇后的位置。
* 找到解决方案就输出,若没有解决方案就回溯到倒数第二个摆放皇后的位置,置为0···依次类推
*
* 心得:
* 需要回溯的点就是调用递归方法的地方。
* 看到回溯算法,因为需要回溯所以想到递归,但是不知何时递归。
* 通过重新学习八皇后的问题,参考网上的一些代码之后,自己写了一遍程序(鄙人不才,只是独立思考,没有写出来)
* 更深刻的了解了递归和回溯算法
*
* 开发递归方法的技巧:
* 1.写功能方法时可以事先假设此功能方法已经实现
* 2.找到回溯的点(每次执行会入栈,执行完跳出栈,继续向下执行的那个点)
* 3.难点:找到跳出递归方法的条件
*/
public class Queen {
/** 角标代表列的位置,值代表可否放置皇后,0代表可以放置皇后,1代表不可以放置皇后 */
private int[] columns;//判断列
/** 角标代表正向对角线(/)的位置,值代表可否放置皇后,0代表可以放置皇后,1代表不可以放置皇后 */
/* 二维数组的一维角标和二维角标相加,即i + j
0 1 2 3 4 5 6 7
1 2 3 4 5 6 7 8
2 3 4 5 6 7 8 9
3 4 5 6 7 8 9 10
4 5 6 7 8 9 10 11
5 6 7 8 9 10 11 12
6 7 8 9 10 11 12 13
7 8 9 10 11 12 13 14
*/
private int[] diagonalPositive;//正向对角线
/** 角标代表反向对角线(\)的位置,值代表可否放置皇后,0代表可以放置皇后,1代表不可以放置皇后 */
/* 二维数组的一维角标和二维角标相减(谁减谁无所谓,只是数值位置不一样而已),即 i - j (因为数据角标是大于0,所以相减后加一定的值使其大于0,即i - j + (temp - 1))
7 6 5 4 3 2 1 0
8 7 6 5 4 3 2 1
9 8 7 6 5 4 3 2
10 9 8 7 6 5 4 3
11 10 9 8 7 6 5 4
12 11 10 9 8 7 6 5
13 12 11 10 9 8 7 6
14 13 12 11 10 9 8 7
心得:两个对象,变化规律:若是一多一少,和相等;同时多或同时少,差相等*/
private int[] diagonalNegative;//反向对角线
/** 二维数组记录解决方案,角标代表棋盘位置(一维角标代表行,二维角标代表列),值代表可否放置皇后,1代表可以放置,0没有放置 */
private int[][] result;
/** 存储几皇后,例如八皇后,值记录8 */
private int n;
/** 统计所有放置皇后的解决方案 */
private int count;
/**
* 构造方法
* @param N 几皇后
*/
public Queen(int N) {
this.n = N;
this.columns = new int[N];
this.diagonalPositive = new int[2 * N - 1];
this.diagonalNegative = new int[2 * N - 1];
this.result = new int[N][N];
}
/**
* 唯一对外开发的方法,输出解决方案
*/
public void showAnswer() {
queen(0);//从第一行开始寻找摆放位置
}
/**
* 寻找摆放皇后的位置
* @param row 行角标,从0开始
*/
private void queen(int row) {
if (row < this.n) {//行数是否超过棋盘角标限制
for (int column = 0; column < this.n; column++) {//遍历每一行的每一列的位置,判断是否可以放置皇后
//计算角标,列和对角线的值都为0,证明这个位置所在的列和对角线上都没有放置皇后
if (columns[column] == 0 && diagonalPositive[row + column] == 0 && diagonalNegative[row - column + this.n - 1] == 0) {
result[row][column] = 1;//解决方案中存储此处放置皇后
columns[column] = diagonalPositive[row + column] = diagonalNegative[row - column + this.n - 1] = 1;//标记此处对应的列和对角线都能放置皇后
queen(row + 1);//寻找下一行放置皇后的位置,同时也是方法执行完成后的回溯点
result[row][column] = 0;//方案输出之后,会回溯到上面的点,重新标记为0,也就是此处不放置皇后,去寻找“这行”下一个摆放皇后的位置(对应这个for循环)
columns[column] = diagonalPositive[row + column] = diagonalNegative[row - column + this.n - 1] = 0;//因为没有放置皇后,所以对应的列和对角线重置为0
}
}//如果遍历完成都没有找“这行”下个放置皇后的位置,证明没有找到解决方案
} else {//行数超过棋盘角标限制,证明已经找完整个棋盘。注意:每一种解决方案,每一行必定有且仅有一个皇后。输出解决方案
System.out.println("第" + ++count + "种解决方案");
show(result);
}
}
/**
* 遍历输出数组的值
* @param arr 目标数组
*/
private void show(int[][] arr) {
for (int[] anArr : arr) {
for (int anAnArr : anArr) {
System.out.print(anAnArr + "\t");
}
System.out.println("\n");
}
}
public static void main(String[] args) {
Queen q = new Queen(4);
q.showAnswer();
}
}