不说废话,看题目:java
如何可以在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都没法直接吃掉其余的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。编程
咱们能够将它看做一个搜索问题:数组
(1)以行为单位,逐行向下搜索。app
(2)每一行都应该搜索全部可能的值。jvm
(3)以前行的皇后的摆放位置会影响后面行皇后的摆放位置。函数
注意第三点,由于咱们每摆放一个皇后,都会由于新皇后的做用域而改变整个棋盘的格局,从而影响后续皇后的摆放。因此咱们在搜索时,一旦一种路径已经搜索完成,咱们须要将尝试该路径时摆放的皇后清空。工具
那么让咱们尝试将每一行须要作的事情写出来:this
遍历该行的每个格子,对每个格子作下面的事:spa
判断该格子是否能够摆放皇后。3d
若是则在该格子摆放皇后,并以此为基础递归搜索下面行全部可能的摆放组合。
若是不能够摆放皇后,什么都不作。
这个格子尝试完了,清空该格子状态,继续for循环尝试本行其它格子。
其中“若是能够摆放皇后,搜索该格子摆放皇后时下面行全部可能的摆放组合。”是一个明显的递归调用,“这个格子尝试完了,清空该格子状态,继续for循环尝试本行其它格子。”是一个明显的回溯点。咱们将上述描述转化为代码:
其实到了这里,实现就只剩下如“如何判断该格子是否能够摆放皇后”等细节问题了。
另外有些朋友可能对回溯的过程有疑问,对递归调用的过程理解的不是很清晰。建议看一下函数栈的结构以及函数是如何运行的,递归函数也是一个普通函数,只是本身调用了本身。执行时无非也是临时变量、寄存器值、ebp、pc指针的压栈弹栈。函数对于底层来讲就是一段指令,递归函数是底层在重复的执行这一段指令,但每次执行时存的临时变量不一样罢了。函数内部调用另外一个函数,保存完自己的执行环境后pc指针指向被调用函数并为被调用函数开辟新的栈帧,递归调用就只是pc指针又指向了本身并为本身又开辟了一个新的栈帧,与其它函数并无什么不一样。
要说不一样的话,对于普通的函数调用咱们是知道或者说能够控制须要调用多少层级的,也就是说咱们须要的栈大小是有限的。而递归调用时,除了本题这种咱们知道调用层级层数的递归(本题8层),其它形式的递归只有到达回归条件才会终止调用。咱们并不知道到达回归条件时函数调用了多少层,若是层级过深极有可能会致使栈过大而引发内存不足。
另外开辟新的栈帧以及保存和恢复函数执行现场是很是消耗时间的,这就致使了递归函数的效率明显低于递推函数,由于要频繁的进行函数调用。在非用递归不可的时候(还没碰到过)能够尝试一下将函数定义为final(java),jvm会尝试将final函数在编译期进行内联,以减小函数调用的发生。
因此在生产环境中咱们应该尽可能避免这种不可控的写法,而应该用尾递归或将递归转化为递推的方式来实现功能。递归只是咱们思考的工具,递归的代码写出来咱们也就对问题的解空间有了一个总体的认识,想象一下递归的回归过程即是递推的过程。
下面放出N皇后完整代码(java版,棋盘大小设为8即为8皇后),有兴趣的盆友能够跑一下:
棋盘类:
package learning; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; public class Chessboard { //棋盘数组 private int[][] chessboard; //棋盘宽度 private int x; //棋盘高度 private int y; public int[][] getChessboard(){ return this.chessboard; } //构造棋盘 public Chessboard(int x, int y) { if (x <= 0 || y <= 0) { throw new RuntimeException("棋盘大小必须为正整数"); } this.x = x; this.y = y; chessboard = new int[x][y]; } public int getX(){ return this.x; } public int getY(){ return this.y; } public void setQueen(int x,int y){ this.chessboard[x][y]=1; } //清除x如下行的棋子 public void clean(int x) { if (x < 0 || x > this.x - 1) { throw new RuntimeException("行号不合法"); } for (int i = x ; i < this.x; i++) { for (int j = 0; j < this.y; j++) { chessboard[i][j] = 0; } } } //打印棋盘 public String print() { if (x <= 0 || y <= 0) { throw new RuntimeException("棋盘大小必须为正整数"); } System.out.println("该棋盘为------>"); for (int i = 0; i < x; i++) { StringBuffer sb = new StringBuffer(); for (int j = 0; j < y; j++) { sb.append(chessboard[i][j]); if (j != y - 1) { sb.append(","); } } System.out.println(sb.toString().trim()); } return null; } //查看该格子可不能够放置皇后 public boolean canSetQueen(int x, int y) { if (x < 0 || y < 0 || x > this.x - 1 || y > this.y - 1) { throw new RuntimeException("格子不在棋盘内!"); } //判断同一列 for (int i = 0; i < this.x; i++) { if (chessboard[i][y] != 0 && i != x) { return false; } } //判断同一行 for (int j = 0; j < this.y - 1; j++) { if (chessboard[x][j] != 0 && j != y) { return false; } } //判断斜线 for (int i = 0; i < this.x; i++) { for (int j = 0; j < this.y; j++) { if (x - i == y - j || i - x == j - y || i - x == j - y || i - x == y - j) { if (chessboard[i][j] != 0) { return false; } } } } return true; } }
主类:
package learning; public class NQueeen { static int num=0; public static void main(String[] args){ Chessboard chessboard=new Chessboard(8,8); setQueens(chessboard,0); System.out.println("共有 :"+num+" 种摆放方式"); } /** * @param chessboard 棋盘对象 * @param x 当前处理行数 * @Discreption N皇后回溯核心方法 */ public final static void setQueens(Chessboard chessboard,int x){ //判断一下行号是否合法 if(x<0||x>chessboard.getX()){ throw new RuntimeException("x is to large than chessboard!"); } //回归条件,搜索到了最后一行 if(x>=chessboard.getX()){ //N皇后摆放完毕,打印棋盘 chessboard.print(); num++; return; } //j为当前行的格子索引 for(int j=0;j<chessboard.getY();j++){ //判断当前格子是否能够摆放皇后 if(chessboard.canSetQueen(x,j)){ //摆放皇后 chessboard.setQueen(x,j); //递归搜索下一行全部的可能 setQueens(chessboard,x+1); } //当前行的当前格子尝试完毕,清除该格子中的皇后,经过for循环尝试该行其它格子 int[][] iii=chessboard.getChessboard(); iii[x][j]=0; } } }
END;
@Author 牛有肉 转载请注明出处