回溯法原理
具体的原理可以看这篇博客。写的非常好,这里引用一下原文。
回溯法有通用解法的美称,对于很多问题,如迷宫等都有很好的效果。回溯算法实际上一个类似枚举的深度优先搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回(也就是递归返回),尝试别的路径。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。回溯法说白了就是穷举法。回溯法一般用递归来解决。
回溯法一般都用在要给出多个可以实现最终条件的解的最终形式。回溯法要求对解添加一些约束条件。总的来说,如果要解决一个回溯法的问题,通常要确定三个元素:1、选择。对于每个特定的解,肯定是由一步步构建而来的,而每一步怎么构建,肯定都是有限个选择,要怎么选择,这个要知道;同时,在编程时候要定下,优先或合法的每一步选择的顺序,一般是通过多个if或者for循环来排列。
2、条件。对于每个特定的解的某一步,他必然要符合某个解要求符合的条件,如果不符合条件,就要回溯,其实回溯也就是递归调用的返回。
3、结束。当到达一个特定结束条件时候,就认为这个一步步构建的解是符合要求的解了。把解存下来或者打印出来。对于这一步来说,有时候也可以另外写一个issolution函数来进行判断。注意,当到达第三步后,有时候还需要构建一个数据结构,把符合要求的解存起来,便于当得到所有解后,把解空间输出来。这个数据结构必须是全局的,作为参数之一传递给递归函数。对于回溯法来说,每次递归调用,很重要的一点是把每次递归的不同信息传递给递归调用的函数。而这里最重要的要传递给递归调用函数的信息,就是把上一步做过的某些事情的这个选择排除,避免重复和无限递归。另外还有一个信息必须传递给递归函数,就是进行了每一步选择后,暂时还没构成完整的解,这个时候前面所有选择的汇总也要传递进去。而且一般情况下,都是能从传递给递归函数的参数处,得到结束条件的。
递归函数的参数的选择,一般要遵循四个原则:1、必须要有一个临时变量(可以就直接传递一个字面量或者常量进去)传递不完整的解,因为每一步选择后,暂时还没构成完整的解,这个时候这个选择的不完整解,也要想办法传递给递归函数。也就是,把每次递归的不同情况传递给递归调用的函数。
2、可以有一个全局变量,用来存储完整的每个解,一般是个集合容器(也不一定要有这样一个变量,因为每次符合结束条件,不完整解就是完整解了,直接打印即可)。
3、最重要的一点,一定要在参数设计中,可以得到结束条件。一个选择是可以传递一个量n,也许是数组的长度,也许是数量,等等。
4、要保证递归函数返回后,状态可以恢复到递归前,以此达到真正回溯。
下面通过一个经典的回溯问题——N皇后问题对回溯法进行讲解。
回溯法之N皇后问题
下面通过3种方法实现N皇后问题,其中:
1. 第一种方法是以最容易理解的方法写的,虽然效率不高,但是对初学回溯的童鞋来说是非常友好的,这种方法认为N皇后的N个皇后都是不相同的;
2. 第二种方法是第一种方法的改进,认为N个皇后都是相同的;
3. 第三种方法是第二种方法的改进,认为第N个皇后放第N行,只需要判断皇后放在那一列即可。
三种方法的具体实现可以看代码。
代码(C++实现)
//N皇后问题
#include <iostream>
#include <vector>
#include <string>
using namespace std;
//=========================方法一=========================//
//最简单的方法,会计算出重复解,认为n个皇后不相同
//void Queen(int n, vector<vector<int>>& board, int t);
//bool Feasible(int row, int col, int n, vector<vector<int>>& board);
//vector<vector<string>> solveNQueens(int n);
//
//vector<vector<string>> res;
//int way = 0;
//
//bool Feasible(int row, int col, int n, vector<string>& board)
//{
// //超出界限
// if (row < 0 || row >= n || col < 0 || col >= n)
// return false;
// //已经有皇后
// if (board[row][col] == 'Q')
// return false;
// //行
// for (int i = 0; i < n; i++)
// {
// if (board[i][col] == 'Q')
// return false;
// }
// //列
// for (int i = 0; i < n; i++)
// {
// if (board[row][i] == 'Q')
// return false;
// }
// for (int i = 1; i < n; i++)
// {
// //左上角
// if ((row - i) >= 0 && (col - i) >= 0) //位置合法
// {
// if (board[row - i][col - i] == 'Q')//此处已有皇后,冲突
// return 0;
// }
// //左下角
// if ((row + i) < n && (col - i) >= 0)
// {
// if (board[row + i][col - i] == 'Q')
// return false;
// }
//
// //右上角
// if ((row - i) >= 0 && (col + i) < n)
// {
// if (board[row - i][col + i] == 'Q')
// return false;
// }
//
// //右下角
// if ((row + i) < n && (col + i) < n)
// {
// if (board[row + i][col + i] == 'Q')
// return false;
// }
// }
// return true;
//}
//void Queen(int n, vector<string>& board, int t)
//{
// if (t == n + 1)
// {
// way++;
// res.push_back(board);
// return;
// }
// else
// {
// for (int i = 0; i < n; ++i)
// {
// for (int j = 0; j < n; ++j)
// {
// //(i,j)位置可以摆放皇后,不冲突
// if (Feasible(i, j, n, board))
// {
// board[i][j] = 'Q'; //摆放皇后t
// Queen(n, board, t + 1); //递归摆放皇后t+1
// board[i][j] = '.'; //恢复
// }
// }
// }
// }
//}
//
//vector<vector<string>> solveNQueens(int n) {
// vector<string> board(n, string(n, '.'));
// Queen(n, board, 1);
// return res;
//}
//=========================方法一=========================//
//=========================方法三=========================//
//没有重复解,注意valid方法的妙用
//int way = 0;
//bool valid(vector<int>& queen_pos, int row, int col) {
// for (int i = 0; i < queen_pos.size(); ++i) {
// if (i == row ||
// col == queen_pos[i] ||
// row + col == i + queen_pos[i] ||
// row + queen_pos[i] == col + i) {
// return false;
// }
// }
//
// return true;
//}
//
//void inner(int n, int index_i, vector<int>& queen_pos, vector<string>& ans, vector<vector<string>>& res) {
// if (n == index_i) {
// res.push_back(ans);
// way++;
// return;
// }
//
// for (int j = 0; j < n; ++j) {
// if (valid(queen_pos, index_i, j)) {
// ans[index_i][j] = 'Q';
// queen_pos.push_back(j);
// inner(n, index_i + 1, queen_pos, ans, res);
// queen_pos.pop_back();
// ans[index_i][j] = '.';
// }
// }
//}
//vector<vector<string>> solveNQueens(int n) {
// vector<int> queen_pos;
// vector<string> ans(n, string(n, '.'));
// vector<vector<string>> res;
//
// inner(n, 0, queen_pos, ans, res);
// return res;
//}
//=========================方法三=========================//
//=========================方法二=========================//
void Queen(int n, vector<vector<int>>& board, int t);
bool Feasible(int row, int col, int n, vector<vector<int>>& board);
vector<vector<string>> solveNQueens(int n);
vector<vector<string>> res;
int way = 0;
bool Feasible(int row, int col, int n, vector<string>& board)
{
//超出界限
if (row < 0 || row >= n || col < 0 || col >= n)
return false;
//已经有皇后
if (board[row][col] == 'Q')
return false;
//行
for (int i = 0; i < n; i++)
{
if (board[i][col] == 'Q')
return false;
}
//列
for (int i = 0; i < n; i++)
{
if (board[row][i] == 'Q')
return false;
}
for (int i = 1; i < n; i++)
{
//左上角
if ((row - i) >= 0 && (col - i) >= 0) //位置合法
{
if (board[row - i][col - i] == 'Q')//此处已有皇后,冲突
return 0;
}
//左下角
if ((row + i) < n && (col - i) >= 0)
{
if (board[row + i][col - i] == 'Q')
return false;
}
//右上角
if ((row - i) >= 0 && (col + i) < n)
{
if (board[row - i][col + i] == 'Q')
return false;
}
//右下角
if ((row + i) < n && (col + i) < n)
{
if (board[row + i][col + i] == 'Q')
return false;
}
}
return true;
}
void Queen(int n, vector<string>& board, int t)
{
if (t == n )
{
way++;
res.push_back(board);
return;
}
else
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
//(i,j)位置可以摆放皇后,不冲突
//注意这里的i==t,只有当第t个棋子时摆第7行才行,不然回溯会有重复解
if (i == t && Feasible(i, j, n, board))
{
board[i][j] = 'Q'; //摆放皇后t
Queen(n, board, t + 1); //递归摆放皇后t+1
board[i][j] = '.'; //恢复
}
}
}
}
}
vector<vector<string>> solveNQueens(int n) {
vector<string> board(n, string(n, '.'));
Queen(n, board, 0);
return res;
}
//=========================方法二=========================//
int main()
{
int n = 0;
cin >> n;
vector<vector<string>> solution = solveNQueens(n);
//打印结果
for (int i = 0; i < solution.size(); i++)
{
for (auto j:solution[i])
{
cout << j << endl;
}
cout << endl << endl;
}
//总共的方式
cout << way << endl;
system("pause");
return 0;
}
结果
如果你只想要一个合适的解,那么你可以将递归函数改成有bool返回值的函数:
类似于下面的函数:
// 函数找到一个答案后就返回 true
bool Queen(int n, vector<string>& board, int t)
{
if (t == n )
{
way++;
res.push_back(board);
return true;
}
else
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
//(i,j)位置可以摆放皇后,不冲突
//注意这里的i==t,只有当第t个棋子时摆第7行才行,不然回溯会有重复解
if (i == t && Feasible(i, j, n, board))
{
board[i][j] = 'Q'; //摆放皇后t
if(Queen(n, board, t + 1))//这里通过有返回值的函数提前终止,得到一个解
return true; //递归摆放皇后t+1
board[i][j] = '.'; //恢复
}
}
}
}
return false;
}