算法分析与设计-八皇后问题(回溯法)

回溯法:

回溯的意义是在递归直到可解的最小问题后,逐步返回原问题的过程,而这里所说的回溯算法实际上是一个类似枚举的搜索尝试方法,它的主题思想是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

回溯算法师尝试搜索算法中最基本的一种算法,其采用了一种“走不通就掉头”的思想,作为控制结构。在使用回溯算法解决问题中每向前走一步都有很多路径需要选择,但当没有决策信息或决策信息不充分时,只好尝试某一路径向下走,直至走到一定程度后得知此路不通时,再回溯到上一步尝试其他路径;当然在尝试成功时,则问题得解而算法结束。

回溯法典型例题-八皇后问题:

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

《算法分析与设计-八皇后问题(回溯法)》

第一种解决方法就是枚举法,这种方法较为简单,共有8^8个状态,但实际搜索当中并不会搜索那么多种状态,因为前面已经说明,回溯法采用的是“走不通就掉头”的策略,所以当我们枚举时候加一些相应的约束条件,可使算法的效率大大提高。

那么怎么确定约束条件呢?

约束条件有三个:

(1)不在同一列,即:xi !=xj(xi ,xj 分别代表第 i ,j 个皇后分别位于第 xi , xj 列);

(2)不在同一主对角线:xi – i != xj – j;

(3)不在同一副对角线:xi + i ! = xj + j;

(2)和(3)又可以合并为一个“不在同一对角线上”的约束条件,表示为:

abs( xi – xj ) != abs( i – j )     abs()代表取绝对值。

故得到算法如下

算法一:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int check(int a[ ],int n)
{
    int i;
    for(i=1; i<=n-1; i++)
        if(abs(a[i]-a[n])==abs(i-n)||(a[i]==a[n]))
            return 0;
    return 1;
}
int main()
{
    int a[9];
    for(a[1]=1; a[1]<=8; a[1]++)
    {
        for(a[2]=1; a[2]<=8; a[2]++)
        {
            if(check(a,2)==0)
                continue;
            for(a[3]=1; a[3]<=8; a[3]++)
            {
                if(check(a,3)==0)
                    continue;
                for(a[4]=1; a[4]<=8; a[4]++)
                {
                    if(check(a,4)==0)
                        continue;
                    for(a[5]=1; a[5]<=8; a[5]++)
                    {
                        if(check(a,5)==0)
                            continue;
                        for(a[6]=1; a[6]<=8; a[6]++)
                        {
                            if(check(a,6)==0)
                                continue;
                            for(a[7]=1; a[7]<=8; a[7]++)
                            {
                                if(check(a,7)==0)
                                    continue;
                                for(a[8]=1; a[8]<=8; a[8]++)
                                {
                                    if(check(a,8)==0)
                                        continue;
                                    else
                                    {
                                        for(int i=1; i<=8; i++)
                                             printf("%d ",a[i]);
                                        printf("\n");
                                    }

                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

枚举算法虽然有很好的可读性,也能从中体会到回溯的思想,但它只能解决“皇后个数为常量”的问题,却不能解决任意的n皇后问题,下面就是典型的非递归回溯算法:

算法二:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int a[30],n;
bool check(int k)
{
    for(int i=1; i<k; i++)
        if(abs(a[i]-a[k])==abs(i-k)||a[i]==a[k])
            return false;
    return true;
}
void output()
{
    for(int i=1; i<=n; i++)
        cout<<a[i]<<" ";
    cout<<endl;
}
void backdate(int n)
{
    int k=1;
    a[1]=0;
    while(k>0)
    {
        a[k]++;
        while(a[k]<=n&&check(k)==false) //为第k个皇后搜索位置
            a[k]++;
        if(a[k]<=n)
        {
            if(k==n)  //找到一组解
                output();
            else
                a[++k]=0;//下一个皇后要从头开始搜索
        }
        else
            k--;//回溯
    }
}
int main()
{
    cin>>n;
    backdate(n);
}

递归回溯算法:

我们用b,d,c数组分别来记录棋盘上的n个列,2n-1个主对角线,2n-1个副对角线的占用情况。

说明:

用i,j分别 表示皇后所在的行列(或者说i号皇后在j列),同一主对角线上的行列下标的差一样,若用表达式 i-j 编号,则范围是    -n+1 ~ n+1,所以用i-j+n表示主对角线编号,范围就是1~ 2n-1。同样地,副对角线上行列下标的和一样,用表达式i+j编号,则范围2~2n。

算法三:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int a[20],b[20],c[20],d[20];
int n,t,k;
void output()
{
    t++;
    cout<<t<<":";
    for(int k=1;k<=n;k++)
        cout<<" "<<a[k];
    cout<<endl;
}
void tryy(int i)
{
    for(int j=1;j<=n;j++)
    {
        if(b[j]==0&&c[i+j]==0&&d[i-j+n]==0)
        {
            a[i]=j; //摆放皇后
            b[j]=1; //占领第j列
            c[i+j]=1; //主对角线
            d[i-j+n]=1;  //副对角线
            if(i<n)
                tryy(i+1); //搜索
            else //找到一组解
                output();
            b[j]=0;
            c[i+j]=0;
            d[i-j+n]=0; //重新设置为0
        }
    }
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        b[i]=0;
        c[i]=0;
        c[n+i]=0;
        d[i]=0;
    }
    tryy(1);
}

    原文作者:回溯法
    原文地址: https://blog.csdn.net/Akatsuki__Itachi/article/details/80803838
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞