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。code

  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。对象

相关文章
相关标签/搜索