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

相關文章
相關標籤/搜索