八皇后问题

本文转自博客 http://blog.youkuaiyun.com/bone_ace/article/details/41419695  在此谢谢原文作者的无私奉献


问题描述:

要在8*8的国际象棋棋盘中放8个皇后,使任意两个皇后都不能互相吃掉。规则是皇后能吃掉同一行、同一列、同一对角线的棋子。如下图即是两种方案:



解决方案:

8*8的棋盘要摆放8个皇后,且不能同行同列同对角线,那么每行必定会有一个皇后。我们可以设一个数组a用来存放每一行皇后的位置,元素值表示第几列(如a[1]=5表示第一行的皇后处于第五个格)。然后只需要求出数组a的值 问题就解决了,下面介绍三种回溯解法:


1、八个for循环。用枚举的办法,八个for循环分别枚举每一行的8个位置,但是我们不用全部枚举完,可以采用“剪枝策略”,即遇到不适合的情况就回溯。例如当a[1]=4,第二行a[2]=4与a[1]同列,不符合题意。接下来的六个循环就不用穷举下去了,直接"continue;"去检验a[2]=5.....具体代码如下:


    void main()  
    {  
        int a[9];  
        int i,t=1;  
        for(a[1]=1;a[1]<9;a[1]++)  
            for(a[2]=1;a[2]<9;a[2]++)  
            {  
                if(!Check(a,2)) continue;  
                for(a[3]=1;a[3]<9;a[3]++)  
                {  
                    if(!Check(a,3)) continue;  
                    for(a[4]=1;a[4]<9;a[4]++)  
                    {  
                        if(!Check(a,4)) continue;  
                        for(a[5]=1;a[5]<9;a[5]++)  
                        {  
                            if(!Check(a,5)) continue;  
                            for(a[6]=1;a[6]<9;a[6]++)  
                            {  
                                if(!Check(a,6)) continue;  
                                for(a[7]=1;a[7]<9;a[7]++)  
                                {  
                                    if(!Check(a,7)) continue;  
                                    for(a[8]=1;a[8]<9;a[8]++)  
                                    {  
                                        if(!Check(a,8)) continue;  
                                        else   
                                        {  
                                            printf("第%d种解法:\n",t++);  
                                            for(i=1;i<9;i++)  
                                                printf("第%d个皇后:%d\n",i,a[i]);  
                                            printf("\n\n");  
    }       }   }   }   }   }   }   }   }  

    /////////////////////////////////Check函数功能:检验第n行的皇后是否和之前的皇后有冲突,没有的话返回1  
    int Check(int a[],int n)  
    {  
        for(int i=1;i<n;i++)  
        {  
            if(abs(a[i]-a[n])==abs(i-n) || a[i]==a[n])//////////////见下面注释  
                return 0;  
        }  
        return 1;  
    }  



代码注释:

某一行的皇后a[n]不能和之前的皇后a[i]位置有冲突,约束条件为:

a、不在同一列:a[n] != a[i]

b、不在同一行:因为现在是每一行求一个皇后的位置,所以同一行不会有冲突,不需要考虑。

c、不在同一对左角线:a[n]-a[i] != n-i

d、不在同一右对角线:a[n]-a[i] != -(n-i)

条件c和d可以合成:abs(a[n]-a[i]) != abs(n-i)

总结:其实这里用到的就是深度优先搜索的思想,从第一行的皇后一直深入去找第二行、第三行...皇后的位置。其中加上了约束条件Check函数进行“剪枝”。这就是回溯算法的思想:深度优先搜索,遇到不满足的情况就进行回溯。



2、方法一的优化。上述代码易读、易懂,但是用八个for循环不免显得很累赘,而且如果要求在100*100的棋盘上放100个皇后这种“N皇后问题“呢?难道用100个for循环吗?我们来把代码优化一下,用到的思想还是和方法一相同:深度优先搜索、回溯。具体代码如下:

    void main()  
    {  
        int a[256]={0};  
        int i=1,j,n,t=1;////////////////////////////////////i表示当前正在搜索第i行的皇后位置  
        printf("请输入几皇后?n=");  
        scanf("%d",&n);  
        while(i>0)  
        {  
            for(a[i]++;a[i]<=n;a[i]++)  
            {  
                if(Check(a,i))//////////////////////////////如果第i行的皇后与之前的皇后位置上没有冲突,则break跳出循环  
                    break;  
            }  
            if(a[i]<=n)/////////////////////////////////////如果a[i]<=n,即上面的for循环是由“break;”跳出来的,即第i行皇后的位置符合条件  
            {  
                if(i==n)////////////////////////////////////找到一组解,输出  
                {  
                    printf("第%d种解法:\n",t++);  
                    for(j=1;j<=n;j++)  
                        printf("第%d个皇后:%d\n",j,a[j]);  
                    printf("\n\n");  
                }  
                else////////////////////////////////////////未找完  
                {  
                    i++;  
                    a[i]=0;  
                }  
            }  
            else  
                i--;////////////////////////////////////////回溯  
        }  
    }  


代码注释:上面用到的Check函数和方法一的Check函数相同。

总结:虽然上面代码中只用到两层循环,但是思想、思路和方法一都是一样的,时间复杂度也是和方法一的时间复杂度相同。当n大于10之后运算就已经比较困难了。



3、递归实现。上面两种方法都是用到了深度优先搜索,而一般而言,深度优先搜索都是可以用递归来实现的。下面我们用递归的方式解决八皇后问题。具体代码如下:

    int a[20],n,i,t=1;////////////////////////////////////////全局变量  
      
    void Try(int i)  
    {  
        int j,k;  
        for(j=1;j<=n;j++)  
        {  
            a[i]=j;  
            if(Check(a,i))///////////////////////////////////////如果第j列不会与之前的皇后冲突  
            {  
                if(i<n)//////////////////////////////////////////如果i<n,即还没有找到八个皇后,继续递归  
                    Try(i+1);  
                else ////////////////////////////////////////////如果找到了一组解就输出  
                {  
                    printf("第%d种解法:\n",t++);  
                    for(k=1;k<=n;k++)  
                        printf("第%d个皇后:%d\n",k,a[k]);  
                    printf("\n\n");  
    }   }   }   }  
      
    void main()  
    {  
        printf("几皇后?n=");  
        scanf("%d",&n);  
        Try(1);  
    }  


 代码注释:

a、此处递归的思路很简单,每一层递归表示一行皇后,j表示列,即a[i]=j表示第i行的皇后位置在第j列。

b、以上用到的Check函数与方法一的Check函数相同。

我不清楚是什么原因,递归的速度竟然明显比前面的两种方法快??



———————————————————————————————————————————————————

以上就是原文的全部内容,本人觉得还是第一种和第二张好理解。

对于递归的那种算法,我还不是很理解,自己需要再想一想。


再次谢谢原作者!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值