八皇后问题

问题介绍

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

分析

 1)向二维棋盘落子,为0的位置表示没有棋子,不为0的位置表示有棋子

 2)先落子后判断还是先判断后落子?先判断后落子。先落子后判断又要重置

 3)需要遍历所有结果。因此得到正确解单独处理,然后需要回溯。

 4)行是跳动的,列是遍历的。我们下棋,先下第一行,然后直接到第二行,第二行还是遍历,找到合适位置,然后到第N-1行;

 当第N行不满足,需要直接回退第N-1,而不用继续N+1。比如行的轨迹可能是0-1-2-3-4-5-6-5-6-5-4-5-6-7;

 而列是需要完全遍历每一个位置,当所有位置不满足,才会回退,上一行继续遍历剩下列,知道每一行的所有列都遍历

 5)判断冲突。当前位置跟其余位置列是否冲突,斜线是否冲突。不用检查行,因为我们只会在每行放一个皇后。检查也只需要往回检查。

使用二维棋盘模拟下棋解法:

package com.zby;

/**
 * @author zby
 * @title QueueQuestionOrignal
 * @date 2019年6月13日
 * @description
 */
public class QueueQuestionOrignal {

    private static int solutions;

    public static void main(String[] args) {
        resolveNQueue(8);
        resolveNQueue(9);
        resolveNQueue(10);
        resolveNQueue(11);
        resolveNQueue(12);
        resolveNQueue(13);
        resolveNQueue(14);
        resolveNQueue(15);
    }

    /**
     * N皇后问题
     * 
     * @param queueNum
     */
    public static void resolveNQueue(int queueNum) {
        Long start = System.currentTimeMillis();
        solutions = 0;
        int[][] chessboard = new int[queueNum][queueNum];
        putQueue(chessboard, 0);
        System.out.printf("%d皇后有%d种解法,耗时%dms\n", queueNum, solutions, System.currentTimeMillis() - start);
    }

    /**
     * 我们只需要递增行,列必须全部遍历
     * 
     * @param chessboard 棋盘
     * @param row 行
     */
    public static void putQueue(int[][] chessboard, int row) {
        if (row == chessboard.length) {
            // printchessboard(chessboard);
            solutions++;
            return;
        }
        for (int clos = 0; clos < chessboard.length; clos++) {
            if (!isConflict(chessboard, row, clos)) {
                // 存放的是当前列号+1,实际上只要不是0都可以
                chessboard[row][clos] = clos + 1;
                putQueue(chessboard, row + 1);
                chessboard[row][clos] = 0;
            }
        }
    }

    /**
     * 只需要往回检查列,左斜线,右斜线
     * 
     * @param chessboard
     * @param row
     * @param clos
     * @return
     */
    public static boolean isConflict(int[][] chessboard, int row, int clos) {
        int step = 1;
        while (row - step >= 0) {
            // 检查列
            if (chessboard[row - step][clos] != 0) {
                return true;
            }
            // 检查左斜线
            if (clos - step >= 0 && chessboard[row - step][clos - step] != 0) {
                return true;
            }
            // 检查右斜线
            if (clos + step < chessboard.length && chessboard[row - step][clos + step] != 0) {
                return true;
            }
            step++;
        }
        return false;
    }

    /**
     * 打印棋盘
     * 
     * @param chessboard
     */
    public static void printchessboard(int[][] chessboard) {
        for (int[] row : chessboard) {
            for (int chess : row) {
                System.out.print(chess + " ");
            }
            System.out.println();
        }
        System.out.println("***************");
    }
}

 结果:

 8皇后有92种解法,耗时2ms
 9皇后有352种解法,耗时2ms
 10皇后有724种解法,耗时8ms
 11皇后有2680种解法,耗时35ms
 12皇后有14200种解法,耗时198ms
 13皇后有73712种解法,耗时1228ms
 14皇后有365596种解法,耗时8045ms
 15皇后有2279184种解法,耗时55463ms

 优化:

 八皇后第一种解法
 1 0 0 0 0 0 0 0
 0 0 0 0 5 0 0 0
 0 0 0 0 0 0 0 8
 0 0 0 0 0 6 0 0
 0 0 3 0 0 0 0 0
 0 0 0 0 0 0 7 0
 0 2 0 0 0 0 0 0
 0 0 0 4 0 0 0 0
 1) 观察8皇后的结果,每一行只有一个皇后,可以把结果简化为{1,5,8,6,3,7,2,4}
 2)二维数组的作用是保存结果,那么使用一维数组完全满足了
 3)皇后位置放的是所在列号+1,那么通过一维数组就可以方便知道每个皇后放在什么地方,第一个皇后放在第0行第0列,第二个放在第1行第4列,等等
 3)皇后位置不是用的1,而是第几个皇后,那么判断斜线有了另一种简化方式。abs(当前列号+1-上列的值)==两个皇后行的距离,则冲突

 使用一维数组解法:

/**
 * @author zby
 * @title QueueQuestionUpgrade
 * @date 2019年6月13日
 * @description
 */
public class QueueQuestionUpgrade {
    private static int solutions;

    public static void main(String[] args) {
        resolveNQueue(8);
        resolveNQueue(9);
        resolveNQueue(10);
        resolveNQueue(11);
        resolveNQueue(12);
        resolveNQueue(13);
        resolveNQueue(14);
        resolveNQueue(15);
    }

    /**
     * N皇后问题
     * 
     * @param queueNum
     */
    public static void resolveNQueue(int queueNum) {
        Long start = System.currentTimeMillis();
        solutions = 0;
        int[] chessboard = new int[queueNum];
        putQueue(chessboard, 0);
        System.out.printf("%d皇后有%d种解法,耗时%dms\n", queueNum, solutions, System.currentTimeMillis() - start);
    }

    /**
     * 我们只需要递增行,列必须全部遍历
     * 
     * @param chessboard 棋盘
     * @param row 行
     */
    public static void putQueue(int[] chessboard, int row) {
        if (row == chessboard.length) {
            // printchessboard(chessboard);
            solutions++;
            return;
        }
        for (int clos = 0; clos < chessboard.length; clos++) {
            if (!isConflict(chessboard, row, clos)) {
                chessboard[row] = clos + 1;
                putQueue(chessboard, row + 1);
                chessboard[row] = 0;
            }
        }
    }

    /**
     * 只需要往回检查列,左斜线,右斜线
     * 
     * @param chessboard
     * @param row
     * @param clos
     * @return
     */
    public static boolean isConflict(int[] chessboard, int row, int clos) {
        for (int i = 1; i <= row; i++) {
            if (chessboard[row - i] == clos + 1) {
                return true;
            }
            if (Math.abs(clos + 1 - chessboard[row - i]) == i) {
                return true;
            }
        }
        return false;
    }

    /**
     * 打印棋盘
     * 
     * @param chessboard
     */
    public static void printchessboard(int[] chessboard) {
        for (int chess : chessboard) {
            System.out.print(chess + " ");
        }
        System.out.println();
        System.out.println("***************");
    }

}

 结果:

 8皇后有92种解法,耗时1ms
 9皇后有352种解法,耗时1ms
 10皇后有724种解法,耗时7ms
 11皇后有2680种解法,耗时32ms
 12皇后有14200种解法,耗时180ms
 13皇后有73712种解法,耗时1063ms
 14皇后有365596种解法,耗时6899ms
 15皇后有2279184种解法,耗时47099ms

结论:

 使用一维数组实际上在递归的时间复杂度没有差别,但是判断冲突效率明显提高,因此耗时减少五分之一左右

posted @ 2018-09-20 16:51  java拌饭  阅读(298)  评论(0编辑  收藏  举报