目录
回溯 (Back Tracking)
回溯可以理解为: 通过选择不同的岔路口来通往目的地(找到想要的结果)
每一步都选择一条路出发, 能进则进, 不能进则退回上一步(回溯), 换一条路再试
树、图的深度优先搜索(DFS)、八皇后、走迷宫都是典型的回溯应用
不难看出来,回溯很适合使用递归
N皇后问题 (N Queens)
把八皇后为例解析:
在8x8格的国际象棋上摆放八个皇后,使其不能互相攻击:任意两个皇后都不能处于同一行、同一列、同一斜线上
请问有多少种摆法?
解决思路
四皇后 – 回溯法
在解决八皇后问题之前, 可以先缩小数据规模, 看看如何解决四皇后问题
步骤:
- 每一行只能摆放一个皇后, 在摆放Q1时, 可以先放到(0,0)位置
- 开始在第二行寻找可摆放Q2皇后的位置, Q2摆放在(2,1)位置
- 开始在第三行寻找可摆放Q3皇后的位置
- 在第三行中没有可摆放Q3皇后的位置, 那么, 需要回溯到寻找Q2皇后位置的步骤, 重新将Q2摆放到(3,1)位置
- 在第三行寻找可摆放Q3皇后的位置, Q3摆放在(1,2)位置
- 开始在第四行寻找可摆放Q4皇后的位置
- 在第四行没有可摆放Q4皇后的位置,需要回溯到寻找Q3皇后位置的步骤; 第三行没有其他可摆放Q3的位置, 那么回溯到寻找Q2皇后位置的步骤;第二行没有其他可摆放Q2的位置, 那么回溯到寻找Q1皇后位置的步骤 . . .
- . . . . . .
四皇后 – 剪枝(Pruning)
在前一行皇后位置确认后, 在下一行寻找摆放皇后位置时, 可以根据前一个皇后位置的特性(不能处于同一行, 同一列, 同一斜线), 跳过一些位置的摆放尝试
八皇后 – 回溯法流程
N皇后问题 – 实现
/**
* N皇后问题
*/
public class NQueens {
public static void main(String[] args) {
new NQueens().placeQueens(8);
}
int[] queens; //存放已摆放皇后的位置
int ways = 0; //有多少种摆法
public int placeQueens(int n){
queens = new int[n];
if(n <= 0) return 0;
place(0); //从第 0 行开始摆放皇后
System.out.println(n+"皇后共有种"+ways+"摆法");
return ways;
}
public void place(int row){
if(row == queens.length){ //最后一个皇后摆放完成
ways++;
return ;
}
for (int col = 0; col < queens.length; col ++) {
if(isValid(row, col)){
queens[row] = col; //第row行皇后摆放坐标
place(row + 1); //开始摆放下一行
}
}
}
/**
* 检查目标位置是否可以摆放皇后
* @param row
* @param col
* @return
*/
private boolean isValid(int row, int col) {
for (int i = 0; i < row; i++) { //检查所有已摆放的皇后位置
if(queens[i] == col) return false; //目标位置处于已摆放皇后的同一列位置
if(row - i == Math.abs(col - queens[i])) return false;
}
return true;
}
}
N皇后问题 – 优化1
/**
* N皇后问题
*/
public class NQueens {
public static void main(String[] args) {
new NQueens().placeQueens(4);
}
boolean[] cols; //标记某一列是否存在皇后
boolean[] leftTop; //左上角->右下角 对角线是否存在皇后
boolean[] rightTop; //右上角->左下角 对角线是否存在皇后
int ways = 0; //总共存在多少中摆法
public int placeQueens(int n){
cols = new boolean[n];
leftTop = new boolean[(n << 1) -1];
rightTop = new boolean[leftTop.length];
if(n <= 0) return 0;
place(0); //从第 0 行开始摆放皇后
System.out.println(n+"皇后共有种"+ways+"摆法");
return ways;
}
public void place(int row){
if(row == cols.length){ //最后一个皇后摆放完成
ways ++;
return ;
}
for (int col = 0; col < cols.length; col ++) {
int ltIndex = row - col + cols.length -1;
int rtIndex = row + col;
if(cols[col]) continue; //目标位置所处列已皇后
if(leftTop[ltIndex] ) continue; //目标位置所处斜线已存在皇后
if(rightTop[rtIndex]) continue; //目标位置所处斜线已存在皇后
cols[col] = true;
leftTop[ltIndex] = true;
rightTop[rtIndex] = true;
place(row + 1);
//开始摆放下一行, 回溯
cols[col] = false;
leftTop[ltIndex] = false;
rightTop[rtIndex] = false;
}
}
}
N皇后问题 – 优化2
/**
* N皇后问题
*/
public class NQueens3 {
public static void main(String[] args) {
new NQueens3().placeQueens();
}
/**
* 标记某一列是否存在皇后(0000 0111: 1表示已存在皇后, 0表示不存在皇后)
* (1) 如何判断第 3列是否存在皇后
* 1 < 3 ==> 0000 1000
* 0000 0111 & 0000 1000 ==> 0000 0000 == 0
* 0000 1111 & 0000 1000 ==> 0000 1000 != 0
* 结果是否为 0; 如果为 0, 说明目标列不存在皇后; 如果不为 0, 说明目标列存在皇后
*
* (2) 如何将第 3 列标识为已存在皇后
* 0000 0111 | 0000 1000 ==> 0000 1111
*
* (3) 如何将第 3 列标识为不存在皇后
* ~ 0000 1000 ==> 1111 0111
* 0000 1111 & 1111 0111 ==> 0000 0111
*/
byte cols;
/**
* 左上角->右下角 对角线是否存在皇后 (0110 0100 1110 0000: 1表示已存在皇后, 0表示不存在皇后, 只需要15位, 所以最高位为0)
*/
short leftTop;
/**
* 右上角->左下角 对角线是否存在皇后 (0110 0100 1110 0000: 1表示已存在皇后, 0表示不存在皇后, 只需要15位, 所以最高位为0)
*/
short rightTop;
int ways = 0; //总共存在多少中摆法
public int placeQueens(){
place(0); //从第 0 行开始摆放皇后
System.out.println("8皇后共有种"+ways+"摆法");
return ways;
}
public void place(int row){
if(row == 8){ //最后一个皇后摆放完成
ways ++;
return ;
}
for (int col = 0; col < 8; col ++) {
int colIndex = 1 << col;
int ltIndex = 1 << (row - col + 7);
int rtIndex = 1 << (row + col);
if((cols & colIndex) != 0) continue;
if((leftTop & ltIndex) != 0) continue;
if((rightTop & rtIndex) != 0) continue;
cols |= colIndex;
leftTop |= ltIndex;
rightTop |= rtIndex;
place(row + 1);
//开始摆放下一行, 回溯
cols &= ~colIndex;
leftTop &= ~ltIndex;
rightTop &= ~rtIndex;
}
}
}