1、回溯的定义
回溯算法的定义:回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。一个典型的应用是走迷宫问题,当我们走一个迷宫时,如果无路可走了,那么我们就可以退一步,再在其他的路上尝试一步,如果还是无路可走,那么就再退一步,尝试新的路,直到走到终点或者退回到原点。
2、回溯的思想
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
1. 定义一个解空间,它包含问题的解;
2. 利用适于搜索的方法组织解空间;
3. 利用深度优先法搜索解空间;
4. 利用限界函数避免移动到不可能产生解的子空间。
3、回溯的要素
在当前场景下,存在若干种选择去操作,有可能两种结果:一是违反相应条件限制,只能返回(back),另一种是该选择选到最后正确并结束。
1. 找到选择;
2. 限制条件,即选择操作在此条件下才进行;
3. 结束。
4、回溯算法的典型应用
(1)走迷宫
#include
#include
#include
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 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
#include
#include
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;
}