回溯法之N皇后问题--得到多解以及只得到一个合适解

回溯法原理

具体的原理可以看这篇博客。写的非常好,这里引用一下原文。

回溯法有通用解法的美称,对于很多问题,如迷宫等都有很好的效果。回溯算法实际上一个类似枚举的深度优先搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回(也就是递归返回),尝试别的路径。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。回溯法说白了就是穷举法。回溯法一般用递归来解决。
回溯法一般都用在要给出多个可以实现最终条件的解的最终形式。回溯法要求对解添加一些约束条件。总的来说,如果要解决一个回溯法的问题,通常要确定三个元素:

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;
}
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值