递归的主要特点如下:
自己调用自己
递归通常不在意具体操作,只关心初始条件和上下层的变化关系。
递归函数需要有临界停止点,即递归不能无限制的执行下去。通常这个点为必须经过的一个数。
算法界中,有五大常用算法:贪心算法、分治算法、动态规划算法、回溯算法、分支界限算法。咱们回溯算法就是五大之一,因为回溯算法能够解决很多实际的问题,尽管很多时候复杂度可能不太小,但大部分情况都能得到一个不错的结果。
对于回溯法的定义,百度百科是这么定义的:
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
对于回溯法,它的核心就是试探和复原。这个自动化的过程就是利用递归去执行,在递归函数执行前去修改尝试,满足条件后向下递归试探,试探完毕后需要将数值复原。在这个试探的过程中找到我们所需要的一个或者所有解。这个我们也俗称暴力。
八皇后问题介绍:
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、 同一列或同一斜线上,问有多少种摆法
N皇后问题来源于八皇后问题,为了简化分析,我们将其改为四皇后问题。
四皇后问题共有两种摆法:
0 1 0 0
0 0 0 1
1 0 0 0
0 0 1 0
0 0 1 0
1 0 0 0
0 0 0 1
0 1 0 0
思路分析:
1.第一个皇后先放第一行第一列,可以摆放则向下一行递归。
2.第二个皇后放在第二行第一列、然后判断是否 OK,如果OK则向下一行递归, 如果不 OK,继续放在第二列、第三列、直至把所有列都放完,看是否有OK的,有则向下一行递归,没有则向上一行回溯。
3.继续第三个皇后,还是第一列、第二列……直到第 四个皇后也能放在一个不冲突的位置,算是找到了一个正确解。如果当前行所有列都冲突,则回溯至上一行。
4.当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解, 全部得到
然后回头继续第一个皇后放第二列,后面继续循环执行 1,2,3,4 的步骤
public class NQueen {
// 皇后个数,也是棋盘行列数
public static int QUEEN = 4;
// 初始化棋盘,二维数组,行在前列在后,记忆行列式
public static int[][] Board = new int[QUEEN][QUEEN];
private static int count;
public static void main(String[] args) {
// 从第0行开始
findCount(0);
System.out.println("摆放一共有" + count + "种");
}
public static void findCount(int row) {
//第0列开始
for (int col = 0; col < QUEEN; col++) {
//检查位置是否可以放棋子
if (checkBoard(QUEEN, row, col, Board) == 1) {
if (row == QUEEN - 1) {
// 如果已经到最后一行,则打印棋盘
printBoard(QUEEN, Board);
count++;
}
// 递归,向下一行前进
if (row < QUEEN - 1) {
findCount(++row);
// 回退上一个栈,向上一行回溯
--row;
}
}
// 重新赋值为0,进行下一次判断
Board[row][col] = 0;
}
}
// 检查棋盘
public static int checkBoard(int QUEEN, int row, int col, int[][] Board) {
// 行方向上,因为每行只放一个元素,且行是递增的,所以不用判断
// for (int i = 0; i < QUEEN; i++) {
// // 判断第row行上是否存在棋子
// if (Board[row][i] == 1) {
// return 0;
// }
// }
// 列方向上
for (int i = 0; i < QUEEN; i++) {
if (Board[i][col] == 1) {
return 0;
}
}
// 右下方向上,因为下一行还没有元素,所以不用判断
// for (int i = row, j = col; i < QUEEN && j < QUEEN; i++, j++) {
// if (Board[i][j] == 1) {
// return 0;
// }
// }
// 左上方向上
for (int i = row, j = col; i >= 0 && j >= 0; i--, j--) {
if (Board[i][j] == 1) {
return 0;
}
}
// 左下方向上,因为下一行还没有元素,所以不用判断
// for (int i = row, j = col; i < QUEEN && j >= 0; i++, j--) {
// if (Board[i][j] == 1) {
// return 0;
// }
// }
// 右上方向上
for (int i = row, j = col; i >= 0 && j < QUEEN; i--, j++) {
if (Board[i][j] == 1) {
return 0;
}
}
// 此点符合要求,可以下
Board[row][col] = 1;
// 可以放
return 1;
}
// 打印棋盘
public static void printBoard(int QUEEN, int[][] Board) {
for (int i = 0; i < QUEEN; i++) {
for (int j = 0; j < QUEEN; j++) {
System.out.print(Board[i][j] + " ");
}
System.out.println();
}
System.out.println("================");
}
}