回溯算法

回溯的描述:

回溯算法将解空间看作一定的结构,通常为树形结构,一个解对应于树中的一片树叶。算法从树根(即初始状态出发),尝试所有可能到达的结点。当不能前行时就后退一步或若干步,再从另一个结点开始继续搜索,直到尝试完所有的结点。也可以用走迷宫的方式去理解回溯,设想把你放在一个迷宫里,想要走出迷宫,最直接的办法是什么呢?没错,试。先选一条路走起,走不通就往回退尝试别的路,走不通继续往回退,直到走遍所有的路,并且在走的过程中你可以记录所有能走出迷宫的路线。

 

回溯与八皇后问题

在国际象棋中,皇后是最强大的一枚棋子,可以吃掉与其在同一行、列和斜线的敌方棋子。八皇后问题是这样一个问题:将八个皇后摆在一张8*8的国际象棋棋盘上,使每个皇后都无法吃掉别的皇后,一共有多少种摆法?

如果使用暴力算法的话要在8*8个格子中选择8个格子来摆放皇后,一共要尝试C864次,十亿级别的尝试次数!

稍加分析,我们可以得到一个不那么暴力的办法,显然,每行每列最多只能有一个皇后,如果基于这个事实进行暴力破解,那结果会好得多。安排皇后时,第一行有8种选法,一旦第一行选定,假设选为(1,i),第二行只能选(2,j),其中j!=i,所以有7种选法。以此类推,需要穷举的情况有8!=40320种。

我们先降下规模来看看4皇后问题:4个皇后在4*4的格子里各自安排不打架,一共有多少种安排方法?

试着来穷举一下,真的需要4!=24次尝试吗?现在我们把第一个皇后放在第一个格子,被涂黑的地方是不能放皇后的。

第二行的皇后只能放在第三格或第四格,按照回溯我们要先尝试放在第三格,则:

这样第三位皇后无论放哪里都难逃被吃掉的厄运。于是在第一个皇后位于1号,第二个皇后位于3号的情况下问题无解。我们只能返回上一步来,给2号皇后换个位置。

显然,第三个皇后只有一个位置可选。当第三个皇后占据第三行蓝色空位时,第四行皇后无路可走,于是发生错误,返回上层调用(3号皇后),而3号也别无可去,继续返回上层调用(2号),2号已然无路可去,继续返回上层(1号),于是1号皇后改变位置如下,继续搜索。

话说到这里,我们对“回溯法”已经有了基本概念。然而所谓知易行难,理解算法和将算法写出来完全是两回事。下面来看看代码:

代码不长,注释很多

#include <iostream>
using namespace std;

bool isOk(int c[], int row, int n); //函数:判断能否在第row行第c[row]列插入一个皇后
void queen(int row, int c[], int n, int& total);    //函数:回溯的核心部分

int main()
{
    int n;  //皇后的数量
    cout << "Please enter the number of queen:\n";
    cin >> n;
    int* c = new int[n];    //记录皇后的位置,例如c[i]的值为j表示第i行的皇后放在第j列
    int total = 0;  //解决方案的种数
    queen(0, c, n, total);
    cout << "The number of solution is:\n" << total;
    return 0;
}

void queen(int row, int c[], int n, int& total)
{
    if (row == n)   //当row等于n的时候表示皇后全部摆放完毕
    {
        for (int i = 0; i < n; i++)
            cout << c[i] << " ";
        cout << endl;
        total++;
    }
    else    //皇后还未摆放完,则执行下面程序
    {
        //遍历所有的列,看看第row行皇后可以放在第几列
        for (int col = 0; col < n; col++)   
        {
            c[row] = col;
            //如果可以放在row行col列则继续摆放下一行
            if (isOk(c, row, n))    
                queen(row+1, c, n, total);
            //如果不可以放在row行col列则试试下一列
        }
        //如果循环了所有的列都不能摆放,则会回溯到前一层函数改变上一行皇后的摆放
    }
}

bool isOk(int c[], int row, int n)
{
    for (int i = 0; i < row; i++)
    {   //第row行皇后不能和任意之前的皇后在同一列或 \方向或 / 方向
        if (c[i] == c[row] || c[row]-row == c[i]-i || c[row]+row == c[i]+i)
            return false;
    }
    return true;
}

 

 

 算法是逐行安排皇后的,其参数row为现在正执行到第几行(row是从0开始的,0表示第一行)。n是皇后数。

 

 

参考博客:https://www.cnblogs.com/bigmoyan/p/4521683.html

 

posted @ 2019-04-22 11:42  路漫漫我不畏  阅读(8986)  评论(1编辑  收藏  举报