关于n-皇后问题的详细解答以及二进制优化过程

        问题描述:N 皇后问题源自国际象棋,所有棋子中权力最大的称为皇后,它可以直着走、横着走、斜着走(沿 45 度角),可以攻击移动途中遇到的任何棋子。N 皇后问题的具体内容是:如何将 N 个皇后摆放在 N*N 的棋盘中,使它们无法相互攻击,并且计算有多少存在的可能。

        首先,我们通过一个5皇后问题来了解一下皇后问题的大概运算过程!

        我们选择从(0,2)的位置开始放入第一个皇后,在有限制的条件下,通过遍历可以很容易的看出,只要最后一层存在可以放的位置,那么就可以摆放n(n也指棋盘的宽度,这里n=5)个皇后。

在看完5皇后问题的部分解后,如何用代码去实现皇后问题的解呢?

第一步  : 用代码去实现限制条件

         对角线可以理解为:|x1-x2|=|y1-y2|

    static boolean visit(int arr[],int i , int j){
        for (int k = 0; k < i; k++) { //k是指行数  j是指列数  因为只有选过后才有限制条件,所以一定存在k小于i(目前已经到达的行数) 
            if(arr[k]==j||Math.abs(arr[k]-j)==Math.abs(i-k))
                return false;
        }
        return true;
    }

第二步 :利用dfs去遍历所有情况并且记录

    static int dfs(int arr[],int i){
        if(i==n){
            return 1;
        }
        ans=0;
        for (int j = 0; j < n; j++) {
            if(visit(arr,i,j)){
                arr[i]=j;
                ans+=dfs(arr,i+1);
            }
        }
        return ans;
    }

        利用ans去记录成功的存在n皇后的记录

全部代码如下:

    static int n;
    static int ans;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
         n = sc.nextInt();
        int arr[] = new int[n];
        System.out.println(dfs(arr, 0));// 0是指从0行开始 
    }
    static int dfs(int arr[],int i){
        if(i==n){
            return 1;
        }
        ans=0;
        for (int j = 0; j < n; j++) {
            if(visit(arr,i,j)){
                arr[i]=j;
                ans+=dfs(arr,i+1);
            }
        }
        return ans;
    }
    static boolean visit(int arr[],int i , int j){
        for (int k = 0; k < i; k++) { //k是指行数  j是指列数  因为只有选过后才有限制条件,所以一定存在k小于i(目前已经到达的行数)
            if(arr[k]==j||Math.abs(arr[k]-j)==Math.abs(i-k))
                return false;
        }
        return true;
    }

总结:可以很简单的看出,以上代码解决n-皇后问题的时间复杂度是0(N^2),我可以用二进制稍微优化一点空间复杂度。

2.二进制优化过程。

        想象一下,上面代码是将每一行选的数加入数组,而我们只使用一个数的二进制来代表一列,1视为可以选,0视为不可以选,如此我们可以节约一个数组的空间!

限制的条件的产生以及代码的实现

        我们需要定义三个变量来控制我们的限制条件分别为 左限制(leftlimit)、列限制(cenlimit)、右限制(rightlimit),限制原理如下图所示:

 以选择(0,3)为例,(0,3)一个位置对第二排的限制和对第三排的限制是存在一定的规律的,并且三个变量规律不同一,所以我们选择使用3个变量 左限制(leftlimit)、列限制(cenlimit)、右限制(rightlimit)来控制这种规律。

         每过一行,每个数的左限制会向左移动一位,右限制会向右移动一位,列限制不改变。由此得到限制条件的代码 (limit暂时不参与讨论)。

代码实现:

        第一、遍历过程中,我们枚举第一行所有的可能性并且在1视为可以选择,0视为不可能选择的情况下,我们需要输入一个数,让他0-n-1位全为1;

int limit = (1<<n)-1;

        第二、找到每一行的限制条件,将左右列三个限制合并在一起,因为int类型有32位,所以为了让8-32位上全为0,而且0-7位上可以正常充当限制条件,我们写出如下代码:      

int pos = limit&(~(leftlimit|rightlimit|conlimit));

 全部代码如下:

   static int n ;
    static int ans=0;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        int limit = (1<<n)-1;
        dfs(limit,0,0,0);
        System.out.println(ans);
    }
    static void dfs(int limit,int leftlimit,int rightlimit,int conlimit){
        if(conlimit==limit){
            ans+=1;
            return ;
        }
        int select ;
        int pos = limit&(~(leftlimit|rightlimit|conlimit));
        while(pos!=0){
            select = pos&(~pos+1);
            pos = pos-select;
            dfs(limit,(leftlimit|select)<<1,(rightlimit|select)>>>1,conlimit|select);
        }
    }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值