Java 八皇后问题求解-递归/回溯

问题描述:

八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当n = 1或n ≥ 4时问题有解[1]。
来源:八皇后问题

这里的求解是利用递归来处理的。(暴力解决法,效率很低)

大体思路如下:
依次摆放每个皇后(后面以棋子代替皇后字样),因为不能是同一行,同一列,同一斜线,所以,至少可以肯定,一行只能摆放一个棋子;
然后按照如下步骤进行摆放:

  1. n个棋子摆放在第n行上面(比如第1个棋子就摆放在第一行),但是不知道要摆放在第几列,这时候使用笨方法,每个棋子都是从第1列开始摆放,摆放上去之后判断这个棋子与前面的 n-1 个棋子中的每一个是不是在同一列,同一斜线上面。(这里不再判断是否是同一行,因为在放置的时候,都是采用第n个棋子就摆放在第n行上面,所以一定都不在同一行上面)
  2. 如果上面的判断结果为真(不在同一列,同一斜线上面),则递归调用去摆放第 n+1 个棋子;
  3. 如果上面的判断结果为假,就将当前的棋子移动到下一列。(每一列的可选范围就是 [0,N)
  4. 递归的退出条件就是 此时的 n > N .(比如八皇后,这个 N 就是8 了);

最终8个棋子会全部放完;但是注意,8个棋子放完这个函数并没有结束。按照上面到步骤1,此时会走到最外层的函数里面执行 把 n 摆放到下一列的操作; 然后等把这个棋子摆放到最后一列,并且里面的递归都结束之后,这个函数才结束。

摆放棋子的逻辑(递归实现):

    public void placePiece(int[] map, int numberN, int[] counts) {

        if (numberN == map.length) {
            counts[0]++; // right count
            System.out.println(formatMap(map));
            System.out.println(graphMap(map));
            return;
        }

        for (int i = 0; i < map.length; ++i) {
            // try place in here.
            map[numberN] = i;
            counts[1]++; // try count
            if (checkConflict(map, numberN)) {
                // can place in here
                // then try place the next piece
                placePiece(map, numberN + 1, counts);
            } /*else {
                // can not place in here, nothing to do, just continue;
                System.out.printf("conflict in (%d,%d)\n", numberN, i);
            }*/
        }
    }

下面给出完整实现:


class Queues {

    public static void main(String[] args) {
        int queues = 8;
        int[] array = new int[queues];
        System.out.println("before: @");
        Queues que = new Queues();
        int[] counts = new int[2];
        que.placePiece(array, 0, counts);
        System.out.printf("after: counts # rightCount= %d, tryCount= %d\n", counts[0], counts[1]);
    }

    /**
     * place queue.
     *
     * @param map     巧妙利用数组索引及数组元素值的组合来表示棋盘上面的任意位置。
     *                比如a[0]=0; 表示是 (0,0); a[0]=1; 表示是 (0,1); a[7]=7; 表示的是(7,7) 的位置。
     * @param numberN 表示的棋盘上面的第几个皇后,也是第几行, 而 map[numberN] 表示的第几列;
     * @param counts  用于统计数量;
     */
    public void placePiece(int[] map, int numberN, int[] counts) {

        if (numberN == map.length) {
            counts[0]++; // right count
            System.out.println(formatMap(map));
            System.out.println(graphMap(map));
            return;
        }

        for (int i = 0; i < map.length; ++i) {
            // try place in here.
            map[numberN] = i;
            counts[1]++; // try count
            if (checkConflict(map, numberN)) {
                // can place in here
                // then try place the next piece
                placePiece(map, numberN + 1, counts);
            } /*else {
                // can not place in here, nothing to do, just continue;
                System.out.printf("conflict in (%d,%d)\n", numberN, i);
            }*/
        }
    }

    /**
     * check conflict.
     * 检查当前放置的棋子是否与之前的冲突(同一行,同一列,同一斜线);
     * numberN 表示是当前要检查的皇后; 那么只需要判断该棋子与之前放置的有没有冲突即可; 与之前放置的都不冲突即为不冲突;
     * 同时, numberN 表示的棋盘上面的第几行, 而 map[numberN] 表示的第几列;
     *
     * @param map     巧妙利用数组索引及数组元素值的组合来表示棋盘上面的任意位置。
     *                比如a[0]=0; 表示是 (0,0); a[0]=1; 表示是 (0,1); a[7]=7; 表示的是(7,7) 的位置。
     * @param numberN the serial number of queue. [0, map.length)
     * @return true if it has no conflict; otherwise it's conflicted;
     */
    private boolean checkConflict(int[] map, int numberN) {
        boolean conflict = false;
        for (int i = 0; i < numberN; ++i) {
            if (map[numberN] == map[i] || Math.abs(numberN - i) == Math.abs(map[numberN] - map[i])) {
                conflict = true;
                break;
            }
        }
        return !conflict;
    }

    private String formatMap(int[] map) {
        StringBuilder sb = new StringBuilder("queues: [ ");
        for (int i = 0; i < map.length; ++i) {
            sb.append(String.format("(%d,%d), ", i, map[i]));
        }
        int last;
        if ((last = sb.lastIndexOf(", ")) != -1) {
            sb.delete(last, last + 2);
        }
        sb.append(" ]");
        return sb.toString().replaceAll(" {2}", "");
    }

    private String graphMap(int[] map) {
        StringBuilder sb = new StringBuilder("queues graph:\n{");
        for (int column : map) {
            sb.append("\t");
            for (int j = 0; j < map.length; ++j) {
                if (j != column) {
                    sb.append("# ");
                } else {
                    sb.append("X ");
                }
            }
            sb.append("\n");
        }
        sb.append("}");
        return sb.toString();
    }

}

输出如下:

before: @
queues: [ (0,0), (1,4), (2,7), (3,5), (4,2), (5,6), (6,1), (7,3) ]
queues graph:
{	X # # # # # # # 
	# # # # X # # # 
	# # # # # # # X 
	# # # # # X # # 
	# # X # # # # # 
	# # # # # # X # 
	# X # # # # # # 
	# # # X # # # # 
}
.... 省略中间到打印
queues: [ (0,7), (1,3), (2,0), (3,2), (4,5), (5,1), (6,6), (7,4) ]
queues graph:
{	# # # # # # # X 
	# # # X # # # # 
	X # # # # # # # 
	# # X # # # # # 
	# # # # # X # # 
	# X # # # # # # 
	# # # # # # X # 
	# # # # X # # # 
}
after: counts # rightCount= 92, tryCount= 15720

这个二维棋盘的打印是不是就很灵性~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值