N皇后问题的求解过程

  1. 无解 最初接触N皇后问题,对于N皇后问题所牵涉的回溯算法一概不知,大脑处于混沌状态。

  2. 穷举法 使用穷举法,先把N皇后在棋盘上的排布的所有情况都列举出来,通过递归程序实现,再定义一个判断函数,从中挑选出合适的答案。 代码:

/*得到所有的排布请款*/
	void putQueens(vector<Chessboard>& chessBoards,Chessboard& chessBoard,int queensNum,int nth){
			if (nth == 0){
				if (isOk(chessBoard)){
					chessBoards.push_back(chessBoard);//从结果中进行筛选
				}
			}
			else
				for (int i = 0;i < queensNum;++i){
					chessBoard[nth - 1][i] = 'Q';
					putQueens(chessBoards,chessBoard,queensNum,nth - 1);
					chessBoard[nth - 1][i] = '.';
				}
		}
		vector<vector<string> > solveNQueens(int n) {
			vector<vector<string> > chessBoards;
			vector<string> chessBoard;
			string tmp(n,'.');
			for (int i = 0;i < n;++i){
				chessBoard.push_back(tmp);
			}
			putQueens(chessBoards,chessBoard,n,n);
			return chessBoards;
		}
		bool isOk(vector<string> & queens)//对棋盘排布进行判断,注意这里是直接对整个棋盘进行判断
		{
			vector<Point> locations;
			vector<bool> isRepeatOnY(queens.size(),false);
			for (int i = 0;i < queens.size();++i)
			{
				for (int j = 0;j < queens.size();++j)
				{
					if (queens[i][j] == 'Q')
					{
						locations.push_back(Point(i,j));
						isRepeatOnY[j] = true;
					}
				}
			}
			
			for (const auto &i : isRepeatOnY)
				if (!i)
					return false;
			for (int i = 1;i < locations.size();++i)
			{
				for (int j = 0;j < i;++j)

					if (abs(locations[i].getY() - locations[j].getY()) == abs(locations[i].getX() - locations[j].getX()))
					{
						return false;
					}
			}
			return true;
		}
};

以上是用穷举法得到的程序,运行效率非常低,当计算8皇后问题时,在我的笔记本上需要2分钟,而在leetcode平台上,直接因为超时而为通过。

  1. 回溯法 回溯法相比穷举法,即不是在每种情况都列举完成后才进行判断,而是直接在每种情况所处的每一步中进行判断,遇到不对的排布时直接pass掉
void putQueens(vector<Chessboard>& chessBoards,Chessboard chessBoard,int queensNum,int nth){
			if (nth == queensNum){
				if (isOk(chessBoard)){
					chessBoards.push_back(chessBoard);
				}
			}
			else{
				chessBoard.push_back(string(queensNum,'.'));
				for (int i = 0;i < queensNum;++i){
					chessBoard[nth][i] = 'Q';
					if (!isOk(chessBoard)){
						chessBoard[nth][i] = '.';//在每种情况产生的过程中进行判断
						continue;
					}
					putQueens(chessBoards,chessBoard,queensNum,nth + 1);
					chessBoard[nth][i] = '.';
				}
			}
		}
		vector<vector<string> > solveNQueens(int n) {
			vector<vector<string> > chessBoards;
			vector<string> chessBoard;
			putQueens(chessBoards,chessBoard,n,0);
			return chessBoards;
		}
		bool isOk(vector<string> & queens)
		{
			vector<Point> locations;
			for (int i = 0;i < queens.size();++i){
				for (int j = 0;j < queens[i].size();++j){
					if (queens[i][j] == 'Q'){
						locations.push_back(Point(i,j));
					}
				}
			}
			for (int i = 1;i < locations.size();++i){
				for (int j = i - 1;j >= 0;--j){
					if (locations[i].getY() == locations[j].getY() || abs((locations[i].getY() - locations[j].getY())) == abs((locations[i].getX() - locations[j].getX())))
						return false;
				}
			}
			return true;
		}
};

回溯法直接让我的代码通过了leetcode的测试,运行时间为107ms。

  1. 回溯法的优化 对回溯法的优化主要是对判断函数的优化,不再是直接判断一个棋盘的布局,而是在向其传入一个点的坐标,这样在判断时时间复杂度能直接降低一个数量级。同时因为判断函数的改进,putQueens函数的第二个参数中对第二个参数也不用在进行push_back操作,而直接可以传入一个引用,这样也不再需要对象的构造和析构、赋值等操作。优化后的判断函数如下:
bool isOk(Chessboard chessBoard,QueenPoint pos)//最初的判断函数直接判断整个棋盘,现在直接向其传入皇后位置的坐标
		{
			int size = chessBoard[0].size();
			for (int i = 0;i < pos.getX();++i){
				for (int j = 0;j < size;++j){
					if (chessBoard[i][j] == 'Q'){
						if (pos.getY() == j || (abs(pos.getX() - i) == (abs(pos.getY() - j))))
							return false;
					}
				}
			}
			return true;
		}

putQueens函数的声明变为:

void putQueens(vector<Chessboard>& chessBoards,Chessboard &chessBoard,int queensNum,int nth);

优化后程序的运行时间降至67ms。

  1. 排列组合法 从N皇后问题的描述中,可以得出一个结论,在一个N×N的棋盘中,没一行有且仅有一个皇后,没一列也是有且仅有一个皇后,即其横纵坐标组成的集合均为:[1,2,3,...,N]。那么如果对一个数列[1,2,3,...,N]进行全排列再从中找出合适的结果,那么得到的便是皇后在棋盘中的坐标,得到的一个合法数列中,数列下标即为皇后的横坐标,数列值则为皇后的纵坐标,那么将全排列和回溯法相结合,遍可以快速得到结果。 代码如下:
bool isValid(QueensPos points,int n){
	for (int i = 0;i < n;++i){
		if (points[n] == points[i] || (n - i) == abs(points[n] - points[i]))
			return false;
	}
	return true;
}

void putQueens(vector<vector<string> > &queens,QueensPos& points/*因为不需要在函数中进行push_back操作,这里传入引用*/,int t,int m,int n){
	if (m == 0){
		vector<string> tmp;
		for (int i = 0;i < n;++i){
			string tmpStr(n,'.');
			tmpStr[points[i]] = 'Q';
			tmp.push_back(tmpStr);
		}
		queens.push_back(tmp);
	}
	else {
		for (int i = t;i < n;++i){
/*以下产生全排列*/
			swap(points[i],points[t]);
			if (!isValid(points,t)){//回溯法进行判断
				swap(points[i],points[t]);
				continue;
			}
			putQueens(queens,points,t + 1,m - 1,n);
			swap(points[i],points[t]);
		}
	}
}
class Solution{
	public:
		vector<vector<string> > solveNQueens(int n){

			QueensPos queens(n);
			for (int i = 0;i < n;++i)
				queens[i] = i;
			vector<vector<string> > result;

			putQueens(result,queens,0,n,n);

			return result;
		}
};

由此,程序的运行时间降至16ms。

转载于:https://my.oschina.net/u/2602561/blog/778242

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值