生活日用算法——八皇后问题

Posted fbw-gxy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了生活日用算法——八皇后问题相关的知识,希望对你有一定的参考价值。

八皇后问题也算是比较经典的回溯算法的经典案例。题干描述如下:

在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法

对此首先我们使用array[][]来构建一个棋盘,然后尝试落子,此时算法如下:

   /**
    * 寻找皇后节点
    * @param row
    * @param size
    */
    public static void findQueen(int row, int size) {
        // 如果皇后行已达到最后一行,结果数量自增,并未输出当前棋盘情况
        if (row == size) {
            resultCount++;
            print(size);
            return;
        }

        // 递归回溯
        for (int column = 0; column < size; column++) {
            // 检查当前节点是否可以放皇后
            if (check(row, column, size)) {
                // 使用皇后占位
                array[row][column] = 1;
                // 查找下一列的可占位节点
                findQueen(row + 1, size);
                // 继续尝试同一行其他列占位前,清空临时占位
                array[row][column] = 0;
            }
        }
    }

其中check方法实现如下:

 /**
     * 判断节点是否合适
     *
     * @param row    行
     * @param column 列
     * @param size   棋盘尺寸
     * @return
     */
    public static boolean check(int row, int column, int size) {

        // 遍历
        for (int rowTemp = row - 1; rowTemp >= 0; rowTemp--) {
            // 验证纵向
            if (array[rowTemp][column] == 1) {
                return false;
            }

            int offset = row - rowTemp;
            int columnLeft = column - offset, columnRight = column + offset;
            // 验证左向
            if (columnLeft >= 0 && array[rowTemp][columnLeft] == 1) {
                return false;
            }

            // 验证右向
            if (columnRight < size && array[rowTemp][columnRight] == 1) {
                return false;
            }
        }

        return true;
    }

完整算法如下:

public class EightTest {
    public static int[] array;//棋盘,放皇后
    public static int resultCount = 0;//存储方案结果数量

    public static void main(String[] args) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        int size = 14;
        array = new int[size];
        findQueen(0, size);
        System.out.println(size + "皇后问题共有:" + resultCount + "种可能,耗时:"+stopwatch.stop().toString());
    }

    /**
     * 寻找皇后节点
     *
     * @param row
     * @param size
     */
    public static void findQueen(int row, int size) {
        // 如果皇后行已达到最后一行,结果数量自增,并未输出当前棋盘情况
        if (row == size) {
            resultCount++;
            print(size);
            return;
        }

        // 递归回溯
        for (int column = 0; column < size; column++) {
            // 检查当前节点是否可以放皇后
            if (check(row, column, size)) {
                // 使用皇后占位
                array[row] = column;
                // 查找下一列的可占位节点
                findQueen(row + 1, size);
                // 继续尝试同一行其他列占位前,清空临时占位
                array[row] = 0;
            }
        }
    }

    /**
     * 判断节点是否合适
     *
     * @param row    行
     * @param column 列
     * @param size   棋盘尺寸
     * @return
     */
    public static boolean check(int row, int column, int size) {

        // 遍历
        for (int rowTemp = row - 1; rowTemp >= 0; rowTemp--) {
            // 验证纵向
            if (array[rowTemp] == column) {
                return false;
            }

            int offset = row - rowTemp;
            int columnLeft = column - offset, columnRight = column + offset;
            // 验证左向
            if (columnLeft >= 0 && array[rowTemp] == columnLeft) {
                return false;
            }

            // 验证右向
            if (columnRight < size && array[rowTemp] == columnRight) {
                return false;
            }
        }

        return true;
    }

    public static void print(int size) {//打印结果
        System.out.println("方案" + resultCount + ":");
        for (int i = 0; i < size; i++) {
            for (int m = 0; m < size; m++) {
                System.out.print((array[i] == m ? 1 : 0) + " ");
            }
            System.out.println();
        }
        System.out.println();
    }
}

继续优化时间复杂度,我们仔细观察输出的棋盘信息,举个例子:

方案92:
0 0 0 0 0 0 0 1 
0 0 0 1 0 0 0 0 
1 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 
0 0 0 0 0 1 0 0 
0 1 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 0 0 1 0 0 0 

来考虑是否能把每一列当做一个二进制数来处理,这样我们判断第N列时,其实对于向上判断,只需要进行位运算即可。具体代码如下:

public class EightTest {

    public static Integer size = 15;
    // 棋盘,放皇后
    public static int[] array = new int[size];

    // 映射
    public static int[] arrayMapping = new int[size];

    // 映射
    public static int[][] arrayMappingDeep = new int[size][size];

    // 记录每一列已经被占用的值——2进制
    public static int a = 0;

    // 存储方案结果数量
    public static int resultCount = 0;


    public static void main(String[] args) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        arrayMapping[0] = 0b1;

        for (int i = 1; i < size; i++) {
            arrayMapping[i] = arrayMapping[i - 1] << 1;
        }

        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                arrayMappingDeep[i][j] = arrayMapping[i] | (i + j >= size ? 0 : arrayMapping[i] << j) | arrayMapping[i] >> j;
            }
        }

        findQueen(0, size);
        System.out.println(size + "皇后问题共有:" + resultCount + "种可能,耗时:" + stopwatch.stop().toString());
    }

    /**
     * 寻找皇后节点
     *
     * @param row
     * @param size
     */
    public static void findQueen(int row, int size) {
        // 如果皇后行已达到最后一行,结果数量自增,并未输出当前棋盘情况
        if (row == size) {
            resultCount++;
            print(size);
            return;
        }

        // 递归回溯
        for (int column = 0; column < size; column++) {

            a = 0;
            for (int i = 0; i < row; i++) {
                a = a | arrayMappingDeep[array[i]][row - i];
            }

            // 检查当前节点是否可以放皇后
            if (check(column)) {
                // 使用皇后占位
                array[row] = column;
                // 查找下一列的可占位节点
                findQueen(row + 1, size);
                // 继续尝试同一行其他列占位前,清空临时占位
                array[row] = 0;
            }
        }
    }

    /**
     * 判断节点是否合适
     *
     * @param column 列
     * @return
     */
    public static boolean check(int column) {

        return (a & arrayMapping[column]) == 0;
    }

    public static void print(int size) {//打印结果
        System.out.println("方案" + resultCount + ":");
        for (int i = 0; i < size; i++) {
            for (int m = 0; m < size; m++) {
                System.out.print((array[i] == m ? 1 : 0) + " ");
            }
            System.out.println();
        }
        System.out.println();
    }
}

本地执行没跑出明细差别,leetcode上前者写法7ms,后者4ms。感觉如果要刷leetcode,需要修改输出格式增加缓存来拼一下时间。 

以上是关于生活日用算法——八皇后问题的主要内容,如果未能解决你的问题,请参考以下文章

算法题(八皇后)

八皇后问题求解java(回溯算法)

八皇后问题算法详解

数据结构与算法: 八皇后问题(详细流程)

回溯算法--八皇后问题

算法——八皇后问题(递归回溯实现)