经典回溯算法——八皇后问题



八皇后问题是由19世纪数学家“搞死先生”(高斯先生)提出的,具体的问题是这样的:

在国际象棋的棋盘中(有8×8格)摆放8个皇后,这八个皇后不能相互攻击到(皇后的攻击方向很广:横着,竖着,斜着都能攻击),即8个皇后不能处于同行、同列、同一正反对角线上,这样就不能相互攻击到了。那么,这样的皇后占位的方法,一共有多少种呢?每一种是怎么样的呢?

 

解题思路

  1. 要在棋盘中放置8个皇后,可以一个一个皇后放置到棋盘里,在放皇后的过程中,棋盘中被该皇后所能攻击到的位置要作出标记,以提示之后将要放置的皇后不能在标记的位置中放置,在没有被标记的地方,下一个皇后是可以放置的。

  2. 假如8个皇后仍未全部放置到棋盘中,而棋盘却被全部标记满,则表示前面的皇后放置位置不能满足要求,最好的解决办法就是将在他之前的那一个皇后位置改变。

  3. 以此类推,直到将所有的皇后都能放入到棋盘中为止。

     

    根据解题思路,若不能放置皇后,则返回到它的前面一个皇后中进行修改位置。这就是一个回溯的过程,所以要解决八皇后摆放的问题,也就是可以用回溯算法来解决问题。

     

    对于要回溯问题的解决方法,可以用迭代法和递归法来实现。在这里,我就用递归的方法来讨论这个八皇后问题的解决方法。

     

    思考解题难点

    皇后占位后,所攻击的范围如何标记?

     

    这个标记的方法是一个难点,也是一个解题的突破口,有的朋友认为,可以用一个二维数组进行标记,刚开始二维数组存放全为0,皇后占位后,皇后的位置标记为2,将该皇后所能攻击到的范围在此二维数组中用1来表示(0表示未被标记,12表示被标记而不能占位)。

    这中方法是可行的,也是可以实现的,但还有一种更好的解决方法,这种方法可以不用二维数组,只用一维数组进行标记即可,那么我在这里就只介绍用一维数组进行标记的方法:

  1. 8皇后放置在8×8的棋盘中,所以每一行,每一列都最多只能有一个皇后;

  2. 因此算法执行过程中,可以用一个递归过程的传参进行每一行的摆放问题;

  3. 因为皇后的攻击范围是同行、同列、同一正反对角线的;

  4. 所以用3个一维数组进行标记即可。(一个列标记数组,一个正对角线标记数组,一个反对角线标记数组)

    为什么不用二维数组?

    在这拿列的标记数组举例子,因为在标记的过程中,如果整列需要标记,那么将这一列都标记为1,若没被占位,用0标记即可,所以不需要用到二维数组浪费空间。

  5. 刚开始数组填充全为0,而后如果有占位就用1来表示。

     

    那么列标记与正、反对角线标记中的数组下标有什么关联呢?

    i表示行,用j表示列,那么,正对角线、反对角线与ij的关系为:正对角线的下标正好是i+j,反对角线的关系正好是i-j+size-1。而且正,反对角线的数组长度应该为2*棋盘边长。(size是棋盘的边长以及皇后的数目)

     

    那么由此可以写代码了:

     

    package queen;
    
    public class Queen {
    	private final int size;// 定义棋盘大小(顺便用来表示皇后的数目)
    	private int[] location;// 用来皇后所在列的位置
    	private int[] colsOccupied;// 占领的列
    	private int[] cross1Occupied;// 占领的正对角线
    	private int[] cross2Occupied;// 占领的反对角线
    	private static int count;// 计算方法有多少种
    
    	private static final int STATUS_OCCUPIED = 1;
    	private static final int STATUS_OCCUPY_CANCELED = 0;
    
    	// 初始化棋盘
    	public Queen(int size) {
    		this.size = size;
    		location = new int[size];
    		colsOccupied = new int[size];
    		cross1Occupied = new int[2 * size];
    		cross2Occupied = new int[2 * size];
    	}
    
    	// 判断这个位置有木有被标记
    	private boolean isOccupied(int i, int j) {
    		boolean a;
    		a = (colsOccupied[j] == STATUS_OCCUPIED)
    				|| (cross1Occupied[i - j + size - 1] == STATUS_OCCUPIED)
    				|| (cross2Occupied[i + j] == STATUS_OCCUPIED);
    		return a;
    	}
    
    	// 建立占领状态
    	private void setStatus(int i, int j, int flag) {
    		colsOccupied[j] = flag;
    		cross1Occupied[i - j + size - 1] = flag;
    		cross2Occupied[i + j] = flag;
    	}
    
    	// 第一种显示皇后占位方法
    	private void printLocation1() {
    		System.out.println("第" + count + "种摆放位置");
    		for (int i = 0; i < size; i++) {
    			System.out.println("行:" + i + "列:" + location[i]);
    		}
    	}
    
    	// 第二种显示皇后占位方法
    	private void printLocation2() {
    		System.out.println("第" + count + "种摆放方式");
    		int[][] printLocation = new int[size][size];
    		for (int i = 0; i < size; i++) {
    			printLocation[i][location[i]] = STATUS_OCCUPIED;
    		}
    		for (int[] i : printLocation) {
    			for (int j : i) {
    				System.out.print(j + " ");
    			}
    			System.out.println();
    		}
    	}
    
    	// 在第i行摆放皇后
    	public void place(int i) {
    		for (int j = 0; j < size; j++) {
    			if (!isOccupied(i, j)) {
    				location[i] = j;// 摆放皇后
    				setStatus(i, j, STATUS_OCCUPIED);
    				if (i < size - 1) {
    					place(i + 1);
    				} else {
    					count++;
    					printLocation1();// 用第一种显示方法显示
    					printLocation2();// 用第二种显示方法显示
    				}
    				setStatus(i, j, STATUS_OCCUPY_CANCELED);
    			}
    		}
    	}
    
    	public void start() {
    		place(0);// 从第0行开始摆放皇后
    	}
    
    	public static void main(String[] args) {
    		new Queen(8).start();
    
    	}
    }
    

你可能感兴趣的:(经典回溯算法——八皇后问题)