经典算法之回溯(BackTracking)

1、回溯的定义

  回溯算法的定义:回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。一个典型的应用是走迷宫问题,当我们走一个迷宫时,如果无路可走了,那么我们就可以退一步,再在其他的路上尝试一步,如果还是无路可走,那么就再退一步,尝试新的路,直到走到终点或者退回到原点。

2、回溯的思想

  回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

1. 定义一个解空间,它包含问题的解;

2. 利用适于搜索的方法组织解空间;

3. 利用深度优先法搜索解空间;

4. 利用限界函数避免移动到不可能产生解的子空间。

3、回溯的要素

  在当前场景下,存在若干种选择去操作,有可能两种结果:一是违反相应条件限制,只能返回(back),另一种是该选择选到最后正确并结束

1. 找到选择;

2. 限制条件,即选择操作在此条件下才进行;

3. 结束。

4、回溯算法的典型应用

(1)走迷宫

#include <iostream>
#include <stack>
#include <vector>
using namespace std;
//声明迷宫数组
vector<vector<int>> MAZE =
{
    { 1,1,1,1,1,1,1,1,1,1,1,1 },
    { 1,0,0,0,1,1,1,1,1,1,1,1 },
    { 1,1,1,0,1,1,0,0,0,0,1,1 },
    { 1,1,1,0,1,1,0,1,1,0,1,1 },
    { 1,1,1,0,0,0,0,1,1,0,1,1 },
    { 1,1,1,0,1,1,0,1,1,0,1,1 },
    { 1,1,1,0,1,1,0,1,1,0,1,1 },
    { 1,1,1,1,1,1,0,1,1,0,1,1 },
    { 1,1,0,0,0,0,0,0,1,0,0,1 },
    { 1,1,1,1,1,1,1,1,1,1,1,1 }
};
//存放方格编号
struct xy_coordinate
{
    int x;
    int y;
};
const int ExitX = 8;        //定义出口的X坐标在第8行
const int ExitY = 10;       //定义出口的Y坐标在第10列

int main(int argc, char const *argv[])
{
    cout << "[迷宫的路径(0的部分)]" << endl; //打印出迷宫的路径图
    for (auto i : MAZE)
    {
        for (auto j : i)
        {
            cout << j << " ";
        }
        cout << endl;
    }
    //结构体入栈,类型为结构体名
    stack<xy_coordinate> st;
    int x = 1;      //入口的X坐标
    int y = 1;      //入口的Y坐标
    MAZE[x][y] = 2;
    //声明结构体对象
    xy_coordinate xy;
    while (x <= ExitX && y <= ExitY)
    {
        MAZE[x][y] = 2;
        if (x == ExitX && y == ExitY)//找到出口
            break;
        if (MAZE[x - 1][y] == 0)//北
        {
            x -= 1;
            xy.x = x;
            xy.y = y;
            st.push(xy);//方格编号入栈
        }
        else if (MAZE[x + 1][y] == 0)//南
        {
            x += 1;
            xy.x = x;
            xy.y = y;
            st.push(xy);
        }
        else if (MAZE[x][y - 1] == 0)//西
        {
            y -= 1;
            xy.x = x;
            xy.y = y;
            st.push(xy);
        }
        else if (MAZE[x][y + 1] == 0)//东
        {
            y += 1;
            xy.x = x;
            xy.y = y;
            st.push(xy);
        }
        else
        {
            MAZE[x][y] = 2;
            st.pop();//方格编号弹栈
            x = st.top().x;//更新编号的信息
            y = st.top().y;
        }
    }
    cout << "[迷宫的路径(2)的部分)]" << endl; //打印出迷宫的路径图
    for (auto i : MAZE)
    {
        for (auto j : i)
        {
            cout << j << " ";
        }
        cout << endl;
    }
    system("pause");
    return 0;
}

(2)八皇后

/*
*什么样的解才是可行的?需要描述出任何两个皇后可以“互相攻击”这样的条件:
*(1)有两个皇后处在同一行:解的结构(x[1], x[2], ….. x[n])已经保证同一行不会出现两个皇后。
*(2)有两个皇后处在同一列:表示为x[i]=x[k],假如在图8.23中出现表示为(1 1 * *)、(4 2 3 2)之类的结点,
*则说明有两个皇后在同一列了。
*(3)有两个皇后处在同一斜线:若两个皇后的摆放位置分别是第i行第x[i]列、第k行第x[k]列,若他们在棋盘上
*斜率为-1的斜线上,满足条件i-x[i]=k-x[k],例如(1 4 3 *)、(4 1 2 *);若他们在棋盘上斜率为1的斜线上,
*满足条件i+x[i]=k+x[k]。将这两个式子分别变换成i-k=x[i]-x[k]和i-k=x[k]-x[i],例如(3 4 1 *)。
*综合两种情况,两个皇后位于同一斜线上表示为|i-k|=|x[i]-x[k]|。
*
*在下面的程序实现中,place(x, k)函数用于判断在第k行第x[k]列放置皇后,是否会与前面摆放好的皇后产生相互攻击。
*只要有某行(第i行)的皇后与这个第k行的皇后处在同一列(x[i]=x[k])或者处在同一斜线(|i-k|=|x[i]-x[k]|),
*则立即返回假(0),表示不可能构成解。
*再接下来,就是在实现问题求解的nQueens(x, n)函数中,从第1行开始,逐行逐列地考察皇后的摆放,
*当遇到某一行所有可能情况试过不必再深入到下一行考察时,及时回溯到上一行,接着考察。
*/
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
/*如果一个皇后能放在第k行第x[k]列,则返回真(1),否则返回假(0)*/
int place(vector<int> &x, int &k)
{
    for (int i = 1; i < k; ++i)
    {
        /*判断是否可以在第k行第x[k]列摆放皇后*/
        if (x[i] == x[k] || abs(x[i] - x[k]) == abs(i - k))
        {
            return 0;
        }
    }
    return 1;
}

void printSolution(const vector<int> &x, const int &n)/*输出求解结果*/
{
    int i, j;
    for (i = 1; i <= n; i++)/*输出第i行*/
    {
        for (j = 1; j <= n; j++)
        {
            if (j == x[i])/*第x[i]列输出Q,其他列输出*号 */
                cout << "Q";
            else
                cout << "*";
        }
        cout << "\n";
    }
    cout << "\n";
}

void nQueens(vector<int> &x, const int &n)/*求解n皇后问题*/
{
    int k = 1;
    x[k] = 0;
    while (k > 0)/*当将所有可能的解尝试完后,k将变为0,结束求解过程*/
    {
        x[k]++;/*x[k]是当前列,进到循环中,立刻就会执行x[k]++,而选择了第1列*/
        while (x[k] <= n && !place(x, k))/*逐列考察,找出能摆放皇后的列x[k]*/
            x[k]++;
        if (x[k] <= n)/*找到一个位置可以摆放皇后*/
        {
            if (k == n)/*已经移到最后一行了,是一个完整的解,输出解*/
                printSolution(x, n);
            else/*没有完成最后一行的选择,是部分解,转向下一行*/
            {
                k++;/*接着考察下一行*/
                x[k] = 0;/*到循环开始执行x[k]++后,下一行将从第1列开始考察*/
            }
        }
        else/*对应x[k]>n的情形,这一行已经没有再试的必要,回溯到上一行*/
            k--;
    }
}

int main(int argc, char const *argv[])
{
    int n = 8;
    vector<int> vec(n+1, 0);
    nQueens(vec, n);
    system("pause");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/daaikuaichuan/article/details/80725038
今日推荐