回溯法解N皇后问题

  回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”到前一个结点,尝试别的路径。
回溯法主要包括两种形式:子集树和排列树。

  • 子集树概念:当所给问题是从n个元素的集合S中找出S满足的某种性质的子集时,相应的解空间树称为子集树。例如,0-1背包问题,要求在n个物品的集合S中,选出几个物品,使物品在背包容积C的限制下,总价值最大(即集合S的满足条件<容积C下价值最大>的某个子集)。
    另:子集树是从集合S中选出符合限定条件的子集,故每个集合元素只需判断是否(0,1)入选,因此解空间应是一颗满二叉树。
  • 排列树概念:当问题是确定n个元素满足某种性质的排列时,相应的解空间称为排列树。排列树与子集树最大的区别在于,排列树的解包括整个集合S的元素,而子集树的解则只包括符合条件的集合S的子集。

  理解不了两种形式的朋友请仔细阅读参考文献一,笔者认为,根结点到每个叶结点都是一条路径,子集树问题是求一条条路径中的部分结点,排列树就是求满足条件的一条条路径。
  在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯,相当于走另外的路径了。始终记住,路径是由一个个结点构成的。
  回溯法有典型的代码框架,参考文献一中有,下面列出的框架是从labuladong文章(参考文献二)中看到的代码框架,个人认为思路很清晰:

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择
在for中还可以用if剪枝,跳过不合法的选项,下面八皇后的C++代码中有体现。
vector<vector<string>> res;
//判断同一行、主对角线(左上方)、副对角线(右上方)是否已经存在皇后。
//由于每一次递归调用传入的列不同,所以不需要判断同一列存在皇后。
//每次递归调用只检查row行前是否冲突,所以第row行下方不用判断。
bool isValid(vector<string>& board, int row, int col)
{
	int n = board.size();
	//检测列是否有皇后冲突
	for (int i = 0; i < n; i++)
	{
		if (board[i][col] == 'Q')
			return false;
	}
	//检测右上方
	for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++)
	{
		if (board[i][j] == 'Q')
			return false;
	}
	//检测左上方
	for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--)
	{
		if (board[i][j] == 'Q')
			return false;
	}
	return true;
}
//路径:board中小于row的那些行都已经成功放置了皇后
//选择列表:第row行的所有列都是放置皇后的选择
//结束条件:row超过board的最后一行
void backtrack(vector<string>& board, int row)
{
	if (row == board.size())//8皇后问题中,row=8时是第9行,此时前8行都放好了皇后,就可以把结果保存起来了。
	{
		res.push_back(board);
		return;//由于if里有return,所以下面不用else。
	}
	int n = board[row].size();
	for (int col = 0; col < n; col++)
	{
                //排除不合法的选择,
		if (!isValid(board, row, col))
			continue;
		//做选择
                board[row][col] = 'Q';
                //进入下一层决策树
		backtrack(board, row + 1);
                //取消选择
		board[row][col] = '.';
	}
}
vector<vector<string>> solveNQueens(int n)
{
        //初始化board为二维容器大小为n*n,都初始化为'.'
	vector<string> board(n, string(n, '.'));
	backtrack(board, 0);
	return res;
}

参考文献:
回溯法实例详解
回溯算法详解(修订版)

posted @ 2020-07-06 21:06  江中之苇  阅读(724)  评论(0编辑  收藏  举报