C++ 算法基础_递归算法_n皇后问题

本文探讨了n皇后问题的解决策略,介绍了使用全排列递归结合回溯法的优化方法,与暴力法进行对比,展示了如何通过减少无效计算提升效率。通过实例代码演示了从n列皇后行号排序的角度,以及如何利用回溯技巧避免重复搜索,最终实现对92种合法方案的高效计算。
摘要由CSDN通过智能技术生成

n皇后问题

思路:

根据上一篇全排列的递归思想,我们接下来讲解n皇后问题,n皇后问题是指在一个n*n的国际象棋棋盘上放置n个皇后,时得这n个皇后两辆均不在同一列、同一行、同一对角线上,求合法方案。例如下图是n=8的情况,图中两两皇后之间符合上述的条件,所以它是一个合法的方案。

在这里插入图片描述
对于这个额问题,如果采用组合数的方法来枚举每一种情况(即从n²个位置中选择n个位置),那么将需要C(n²,n)=n²!/((n²-n)!*n!)的枚举量,当n=8时就是54 502 232次枚举,如果n更大,那么就无法承受。

但是换个思路,考虑到每行只能防止一个皇后,每列也只能防止一个皇后,那么如果把n列皇后所在的行号依次写出,那么就会是1~ n的所有排序。对于上面图片的排序就是61528374和36271485。于是只需要枚举1~n的所有排序,查看每个排序对应的位置方案是否合法,统计其中合法的方案即可。由于总共有n!个排序,因此当n=8时只需要40320次枚举,比之前的做法优秀许多。

于是可以在全排列的代码基础上进行求解。由于当到达递归边界时表示生成一个排序,所以需要在其内部判断是否是合法方案,即遍历每两个皇后你,判断他们是否在同一对角线上(不在同一列和同一行是显然的),若不是,则累计计数变量count即可。代码如下,当n=8时count最后等于92。

代码:

#include<iostream>
using namespace std;
const int maxn=11;
int n,P[maxn],hashTable[maxn]={false};
int conut=0;
void generateP(int index)
{
    if(index==n+1)    //递归边界,生成一个排序
    {
        bool flag=true;   //flag为true表示当前是一个合法的方案
        for(int i=1;i<=n;i++)      //遍历任意两个皇后
            for(int j=i+1;j<=n;j++)
                if(abs(i-j)==abs(P[i]-P[j]))  //如果在一条对角线上
                    flag=false;  //不合法
        if(flag)
            conut++;  //若当前方案合法
    }
    for(int x=1;x<=n;x++)   //和全排列一样
    {
        if(hashTable[x]==false)
        {
            P[index]=x;   //令p的第index位为x,即把x加入当前排序
            hashTable[x]=true;   //记x已经在p中
            generateP(index+1); 


  //处理排序的第index+1号位
            hashTable[x]=false;   //已经处理玩P[index]为x的子问题,还原状态 
        }   
    }
}
int main()
{
    n=8;               //8皇后
    generateP(1);      //从1开始选
    cout<<conut<<endl;
    return 0;
}

运行结果如下:

92

优化:

上面这种枚举所有情况,然后判断每一种情况是否合法的做法是非常朴素的(因此一般把不使用优化算法、直接用朴素算法来解决问题的做法称为暴力法)。事实上,通过思考可以发现,当已经放置了一部分皇后时(对应于生成了排列的一部分),可能剩余的皇后无论怎样放置都不可能合法,此时就没必要往下递归了,直接返回上层即可,这样可以减少很多计算量。

一般来说,如果在到达递归边界前的某层,由于一些事实导致已经不需要往任何一个子问题递归,就可以直接返回上一层。一般把这种做法称为回溯法

下面的代码都采用了回溯的写法,请读者体会它与上面代码的区别:

代码:

void generateP1(int index)
{
    if(index==n+1)    //递归边界,生成一个合法方案
    {
        conut++;  //能达到这里的一定合法
        return;
    }
    for(int x=1;x<=n;x++)   //第x行
    {
        if(hashTable[x] ==false)  //第x行还没有皇后
        {
            bool flag=true;//flag为true表示当前皇后不会和之前的皇后冲突
            for(int pre=1;pre<index;pre++)  //遍历之前的皇后
            {
                //第index列皇后的行号为x,第pre列皇后的行号为P[pre]
                if(abs(index-pre)==abs(x-P[pre]))
                {//可以记为(列-列==行-行)
                    flag=false;  
                    break;
                }
            }
            if(flag)
            {
                P[index]=x;  //令第index列皇后的行号为x
                hashTable[x]=true;   //记x已经在p中
                generateP1(index+1);   //处理排序的第index+1号位
                hashTable[x]=false;   
            }
        }   
    }       
}
如果喜欢博主的文章,那就给博主点个关注吧(羞羞羞(@^ _ ^@))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值