zoukankan      html  css  js  c++  java
  • 八皇后问题

    问题介绍

      八皇后问题是一个以国际象棋为背景的问题:如何能够在 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
    ", 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
    ", 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
    

    结论:

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

  • 相关阅读:
    MySQL行级锁和表级锁
    轮询、长轮询、长连接、socket连接、WebSocket
    Http请求的TCP连接
    TCP的三次握手和四次挥手
    面试:做过sql优化吗?
    Java线程池
    C#代码审查工具 StyleCop
    C#中图片切割,图片压缩,缩略图生成的代码
    一个对称加密、解密的方法C#工具类
    C# 音频操作系统项目总结
  • 原文地址:https://www.cnblogs.com/zby9527/p/9681833.html
Copyright © 2011-2022 走看看