杭电OJ——ACM 2553.N皇后问题

N皇后问题

杭电OJ——ACM 2553.N皇后问题链接入口
在这里插入图片描述
问题描述
       有一个n行n列的棋盘,你手里有个n个棋子(即皇后),每下一枚棋子,棋子所处的同行、同列、两条对角线,都不能再放置棋子了,如下图。现在,键盘交给你,写一个程序,计算n颗棋子在n*n棋盘上的放置方法总数。

算法思想:
       这道题用到的算法思想是递归和回溯,接下来我简要讲一下我对递归和回溯的理解,如果有任何错误的地方或者更好的说法,请大家多多指正。
       递归:递归就是一种不断调用自身函数的算法,举个最经典的例子:“从前有座山,山上有座庙,庙里有个老和尚在讲故事,讲的是什么呢?讲的是,从前有座山,山上有座庙。。。”,这样一种不断调用自身的算法。不过,递归算法必须要有一个结束条件。
       递归算法的目的是:将难度规模为1的问题通过递归求出难度规模为n的问题,但前提是不同难度规模问题求解的方法一致。
       回溯:回溯是一种从一个初始状态出发,不断往下一个最优状态探索,如果没办法再往下探索,或者是下一个可探索状态不是优解,就往回退一步,直到最后得出结果的算法。
       在该题上的理解就是,如果皇后从第一行开始放第一个,记录下来限制条件,往下一行放下一个皇后,如果下一行由于与限制条件冲突,放不下,就需要回退到上一行的皇后,重新放置到该行的另一位置。

解题思路:

       首先,出于对时间限制的考虑,先将1到10个的皇后数进行运算得到结果存放在结果数组内。然后,我们只要根据输入的皇后数,输出对应下标的数组结果输出即可。
       其次,我们就可以开始构建算法。了,首先是递归算法,递归结束条件是“k>n”,每次递归的时候都是进入下一行的放置,就要k++,意思即是已经放置到了第几个皇后,以及是已经放置到了第几行,直到第n个放置完毕。
       再说到回溯算法,每当递归一行放置成功时,都会记录下已被占据的行、列、对角线。如果,递归到下一行时,该行的所有位置都在已被占据的位置里,则都不符合条件,无法向下一行递归。这是就会回退了,回退到上一行重新放置,在上一行已被占据的行、列、对角线的记录清空。放置成功后,继续递归,再判断能不能继续递归到下一行,无法继续递归则回溯。
       最后,就是说到:我们怎么记录被占领的行、列、主对角线、副对角线呢?由于每次递归都是向下一行继续放置,所以占据行不需记录。在这里,我定义了三个数组,分别记录被占据的列、主对角线、副对角线。占据列的记录容易做到,只需要你放置的时候,将循环的列数对应的下标数的数组元素赋为1即可。说到两条对角线,需要你去找到规律,将数组里对应下标的元素赋为1即可表明已被占领。我这里用的是 坐标法 去找对角线的规律,我们可以从下图看出不同位置的对角线有其规律:

主对角线的横纵坐标轴相加等于一个常数

副对角线的横纵坐标轴相减等于一个常数

C++版AC代码

#include<iostream>
using namespace std;
#include<math.h>
int n,sum,result[15],xflect[15],lflect[30],rflect[30];
void dfs(int k){
    if(k>n){
        sum++;
        return ;
    }
    for(int i=1;i<=n;i++){
        if(xflect[i]==0&&lflect[k+i]==0&&rflect[k-i+n]==0){
            xflect[i]=1;
            lflect[k+i]=1;
            rflect[k-i+n]=1;
            dfs(k+1);
            xflect[i]=0;
            lflect[k+i]=0;
            rflect[k-i+n]=0;
        }
    }
}
int main(){
    for(int i=1;i<=10;i++){
        n=i;
        sum=0;
        dfs(1);
        result[i]=sum;
    }
    cin>>n;
    while(n!=0){
        cout<<result[n]<<endl;
        cin>>n;
    }
    return 0;
}

C++运行展示:
在这里插入图片描述

JAVA版AC代码

import java.util.Scanner;
class dfs{
    int n,sum;
    int result[] = new int[15];
    int xflect[] = new int[15];
    int lflect[] = new int[30];
    int rflect[] = new int[30];
    public dfs(){}
    public void dfs1(int k){
        if(k>n){
            this.sum++;
            return ;
        }
        for(int i=1;i<=n;i++){
            if(xflect[i]==0&&lflect[k+i]==0&&rflect[k-i+n]==0){
                xflect[i]=1;
                lflect[k+i]=1;
                rflect[k-i+n]=1;
                this.dfs1(k+1);
                xflect[i]=0;
                lflect[k+i]=0;
                rflect[k-i+n]=0;
            }
        }
    }
}
class Main{
    public static void main(String[] args) {
        dfs d = new dfs();
        Scanner scan =new Scanner(System.in);
        for(int i=1;i<=10;i++){
            d.n=i;
            d.sum=0;
            d.dfs1(1);
            d.result[i]=d.sum;
        }
        d.n = scan.nextInt();
        while(d.n!=0){
            System.out.println(d.result[d.n]);
            d.n = scan.nextInt();
        }
        scan.close();
    }
}

JAVA运行展示:
在这里插入图片描述

----------EOF----------

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

peng_YuJun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值