N皇后有关问题的递归回溯实现

N皇后问题的递归回溯实现

时间:2014.04.24

地点:基地

--------------------------------------------------------------------------------

一、题目

  今天看到了8皇后问题,是个非常经典的练习递归和回溯的典型问题。所谓的8皇后,就是在8x8的棋盘上,8个皇后任意两个之间不能同行不能同列,还得不能同那根斜率为正负1的倾斜线。可能的方法如下图。现在要求找出所有可能的符合要求的放法数。

N皇后有关问题的递归回溯实现

把8皇后延生到N皇后,求得N皇后问题的方法数f(N),先给出正确的测试结果有:

f(1)=1     f(2)=0     f(3)=0     f(4)=2     f(5)=10     f(6)=4     f(7)=40     f(8)=92     f(9)=352     f(10)=724

f(11)=2680   f(12)=14200   f(13)=73712

--------------------------------------------------------------------------------

三、回溯法思想

  回溯法也叫试探法,即从问题的某个状态出发,不断地去试探可能的方法,若果碰上死胡同了,可回来,继续试探其他的路径,这种可进可退的方法就叫做回溯法,在很多场合都有应用,比如图的遍历。

--------------------------------------------------------------------------------

四、解决皇后问题的思路

  按照要求,皇后们不能同行同列,对于N皇后问题,我们可用一个N维的数组,数组中存储每行中放置皇后的列数,比如array[row_j]=col_i表示棋盘中第row_j行的皇后放在第col_i列,这样做的好处就是,可以保证每行都只有一个皇后,大大简化了问题,现在我们可以从初始位置逐一试探是否可以放置皇后,比如在棋盘的[0][0]位置处,刚开始显然是可以放的,放好之后,再试探下一行,对于下一行的处理因为有N个元素(N列),我们也逐一试探,比如[1][0],显然因为上面的[0][0]已经有皇后了,不能再放,跳到下一元素[1][1],和[0][0]成对角线,也不能放,在试探下一个元素[1][2],这个就可以了,于是我们在[1][2]位置上安排第二行的皇后,并开始在第2行按同样的方法寻找可放置皇后的列。若如此一直往下推,当要寻找的下一行编号等于N时,说明已经摆好皇后了,是一种可行的方案,于是我们就将计数加一,或者打印方案等,然后,还回上一行,把皇后放到下一个位置,看还行不行,即再寻找一种新的方案。若果还没达到下一行编号为N行,且当前这一行即便把所有可能的列位置遍历完了也没找到可以放置皇后的位置,那么,我们只好回到上一行,将皇后放到该行下一个位置,看行不行。

  如此,总结来说,这里递归和回溯的思想就是:

1.从棋盘的第一行开始,逐步对每一行的列做检测,看能不能放皇后

2.若某一行(row)找到一个可放皇后的位置,我们再递归地去下一行(row+1)可放皇后的位置

2.1.按上面步骤不断往下推进,当下一行为N时,也就是说下一行不存在了时,表明N个皇后已经安排好,方案计数加1

3.上面的情况,当正确的放完皇后也好,还有就是某一行上,寻遍所有的列数都不能放皇后,我们都返回到上一行,去试探下一个位置,寻找另外一种可能的新方案。

--------------------------------------------------------------------------------

五、实现

5.1检测位置[i][j]放置皇后的可行性

  上面已经提到过,由于用一维数组的方式去模拟棋盘,每个数组元素存储的是该行放置皇后的列数,所以已经能保证每一行都只有一个皇后,对于不同列的问题也好处理,即对前面所有元素遍历,看他们存的列数和当前列是不是相等,相等则说明该列已经放过皇后了,我们不能再放,是不合法的位置。然后还有斜线上,这些斜线都是斜率为45度的斜线,啊,也就是如,若果两个元素处在这样的斜线上,那么它们的横坐标之差和纵坐标之差的绝对值是相等的。这样,我们可把这个检测某个位置是否合法的可实现如下:

bool CanPlace(const vector<int>& chess_board, int row, int col)
{
	for (int i = 0; i < row; ++i)                                 //对前面行的皇后们遍历
		if (abs(row - i) == abs(col - chess_board[i])||col==chess_board[i]) 
			return false;      //若给定位置有和前面皇后有列冲突或线冲突,表示不合法
	return true;
}
5.2递归加回溯遍历所有可能的方案
void FindIvalidQueen(vector<int>& chess_board, int size, int row, int* count)
{
	if (size == row)        //表示当前已经将皇后全安置好,
		++(*count);     //方案计数加1,size==row而不是row==size也是有讲究的~~~
	else
	{       //若皇后还没安置完,那么开始遍历该行的每个元素,
		//注意是每一个我们都会试,或许没成功,就试下一个,
		//或许成功了,我们先做下一步处理,但后面的元素我们也是迟早要试的,
		//这就是回溯的精华所在:就是在这个循环中,我们迟早都会去尝试每个元素的,
		//只是迟早的问题,总有一天会回到这个地方,继续往下推进的
		for (int col_i = 0; col_i < size; ++col_i)
		{
			if (CanPlace(chess_board, row, col_i))  //检测当前位置的合法性
			{
				chess_board[row] = col_i;           //合法则放置皇后,并进入试探下一行
				FindIvalidQueen(chess_board, size, row + 1, count);
			}
		}
	}
}

--------------------------------------------------------------------------------

六、完整代码

#include<iostream>
#include<vector>
using namespace std;
bool CanPlace(const vector<int>& chess_board, int row, int col)
{
	for (int i = 0; i < row; ++i)
		if (abs(row - i) == abs(col - chess_board[i])||col==chess_board[i])
			return false;
	return true;
}
void FindIvalidQueen(vector<int>& chess_board, int size, int row, int* count)
{
	if (size == row)    //表示当前已经将皇后全安置好,
		++(*count);     //方案计数加1,size==row而不是row==size也是有讲究的~~~
	else
	{   //若皇后还没安置完,那么开始遍历该行的每个元素,
		//注意是每一个我们都会试,或许没成功,就试下一个,
		//或许成功了,我们先做下一步处理,但后面的元素我们也是迟早要试的,
		//这就是回溯的精华所在:就是在这个循环中,我们迟早都会去尝试每个元素的,
		//只是迟早的问题,总有一天会回到这个地方,继续往下推进的
		for (int col_i = 0; col_i < size; ++col_i)
		{
			if (CanPlace(chess_board, row, col_i))  //检测当前位置的合法性
			{
				chess_board[row] = col_i;           //合法则放置皇后,并进入试探下一行
				FindIvalidQueen(chess_board, size, row + 1, count);
			}
		}
	}
}
int main()
{
	int size;
	cout << "Please input queen's number: " << endl;
	cin >> size;
	vector<int> chess_board(size, 0);
	int count = 0;
	int* p = &count;
	FindIvalidQueen(chess_board,size,0, p);
	cout<<"The possible stragies' number: "<< count<<endl;
	return 0;
}