-
无解 最初接触N皇后问题,对于N皇后问题所牵涉的回溯算法一概不知,大脑处于混沌状态。
-
穷举法 使用穷举法,先把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平台上,直接因为超时而为通过。
- 回溯法 回溯法相比穷举法,即不是在每种情况都列举完成后才进行判断,而是直接在每种情况所处的每一步中进行判断,遇到不对的排布时直接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。
- 回溯法的优化 对回溯法的优化主要是对判断函数的优化,不再是直接判断一个棋盘的布局,而是在向其传入一个点的坐标,这样在判断时时间复杂度能直接降低一个数量级。同时因为判断函数的改进,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。
- 排列组合法 从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。