问题描述:Leetcode 51和52
n皇后问题是指在一个n*n的棋盘上放置n个皇后,使其满足n个皇后之间不能相互攻击:没有两个皇后同时在棋盘的同一行或者同一列或者同一条斜线上。
适用方法:回溯法,回溯法意即从一个点往前走,满足条件继续往前走,不能满足某个要求的话回退回来,试其他的路径。
对于n皇后问题来说回溯法体现在:从当前行开始,从第0列开始搜索,直到找到某列上可以放置皇后,那么继续下一行从第0列搜索。如果当前列上找不到可以放置皇后的列,则回溯至当前行的上一行,将其上的皇后从它所在的列向后搜索,如果回溯到的上一列向后搜索后找不到可以放置,则继续向上回溯,继续搜索,如果直到第一行上也找不到可以放置皇后的位置,那么程序终止。如果到最后一行,找到可以放置皇后的位置,记录当前的这个解,并在最后一行当前列的下一列继续搜索。(最后这句话可以这么理解,找到一个解后由于要找所有的解,找当前列的下一个位置,如果不能满足的话我们就要向上回溯,那么其实就相当于最后一行之上的各行皇后的布局发生了变化,然后我们再在最后一行进行搜索,这其实就相当于查找所有的n-Queens的解)。
n皇后问题使用一个一维数组的数据结A[n]构即可,一维数组的下标表示当前行,其对应的元素值表示当前行皇后所在的列。这样的一种数据结构保证了所有的皇后不在不一行,那么检测的时候只需进行列和斜线检测即可。对于列上检测时,如果检测当前行(记作row)的第j列,那么只需要判断当前行之上的各行,用i表示,皇后位置是否在第j列,即判断A[i]是否等于j。对于斜线上的检测,牛人们发现如果abs(row - i)== abs(j - A[i]),那么斜线上存在冲突。之所以是只对当前行之上的各行进行检测,是因为我们当前行的皇后位置确定了我们才进行其下各行的皇后位置确定。
n皇后主要的两种方法:
1. iterative
这种方法充分的体现了回溯法的本质。这个方法来自这篇博客 http://blog.csdn.net/hackbuteer1/article/details/6657109
class Solution { public: void add_answer(vector<vector<string> > &result,vector<int> &answer) { vector<string> one_answer; char *row = new char[answer.size() + 1]; for(int i = 0;i < answer.size();i++) { for(int j = 0;j < answer.size();j++) { row[j] = (answer[i] == j ? 'Q':'.'); } row[answer.size()] = '\0'; string temp(row); one_answer.push_back(temp); } result.push_back(one_answer); delete []row; } bool put_queen(int row,int column,vector<int> &queen_position) { for(int i = 0;i < row;i++) { if(queen_position[i] == column || abs(row - i) == abs(column - queen_position[i])) return false; } return true; }
void queen(vector<vector<string> > &result,int n) { vector<int> queen_column(n,-1); int i = 0,j = 0; while(i < n) { while(j < n) { if(put_queen(i,j,queen_column)) { queen_column[i] = j; //++i; j = 0; break; } else ++j; } if(queen_column[i] == -1)//当前行没有可以放置皇后的位置,进行回溯 { if(0 == i)//回溯至第一行,由于没有可以放置皇后的位置了,程序退出 break; --i; j = queen_column[i] + 1;//原来皇后所在位置的下一列继续搜索,当前行的皇后位置重新搜索 queen_column[i] = -1; continue; } if(i == n-1)//到最后一行记录一个解,最后一行当前列下一个位置继续搜索 { add_answer(result,queen_column); j = queen_column[i] + 1; queen_column[i] = -1; continue; } ++i; } } vector<vector<string>> solveNQueens(int n) {//程序的方案 vector<vector<string> > result; if(n == 1) { vector<string> answer; answer.push_back("Q"); result.push_back(answer); } else if(n >= 4) { queen(result,n); } //n 为2或3的时候无解 return result; } };2. Recursive的方法
class Solution { public: void add_answer(vector<vector<string> > &result,vector<int> &answer) { vector<string> one_answer; char *row = new char[answer.size() + 1]; for(int i = 0;i < answer.size();i++) { for(int j = 0;j < answer.size();j++) { row[j] = (answer[i] == j ? 'Q':'.'); } row[answer.size()] = '\0'; string temp(row); one_answer.push_back(temp); } result.push_back(one_answer); delete []row; } bool can_put(vector<int> &columns,int col) { for(int i = 0;i < columns.size();i++) { if(columns[i] == col || abs(col - columns[i]) == abs(columns.size() - i)) return false; } return true; } void queen(vector<vector<string> > &result,int n,vector<int> &columns,int num) { if(num == n) add_answer(result,columns);//记录一个解 for(int i = 0;i < n;i++) { if(can_put(columns,i)) { columns.push_back(i); queen(result,n,columns,num+1);//下一行 columns.pop_back(); } } } vector<vector<string>> solveNQueens(int n) { vector<vector<string> > result; vector<int> queen_position; if(n == 1) { vector<string> answer; answer.push_back("Q"); result.push_back(answer); } else if(n >= 4) { queen(result,n,queen_position,0);//第0行开始搜索 } //n 为2或3的时候无解 return result; } };
对这两种方法,发现在leetcode中的n-Queens 2的问题中,第一种方法会超时。我认为可以解释为如下原因:在第一种iterative的方法中,由于是我们先确定之前行然后对其后的行进行查找,如果找不到要向上回溯,那么回溯之后各行的布局发生了变化,再继续进行搜索。而对于recursive的方法中,我们是先找到当前行的皇后位置,然后在剩余各行进行搜索方案的数目,不满足条件的不进行方案的计数,满足的则继续向下搜索,不是按照的这种向上进行回溯,,然后修改上面行的布局的方式。它是直接先确定前面行然后在其后各行空间搜索,满足的计数,不满足则遍历其他方案。粗略的理解可以认为并没有进行回溯。
现在觉得这种解释的方式是不对的,也许是自己的程序代码中某一步出现了问题。因为其实本质都是在进行回溯。
Leetcode 52中要找到所有的答案数目,直接贴出代码吧
class Solution { public: bool can_put(vector<int> &columns,int col) { for(int i = 0;i < columns.size();i++) { if(columns[i] == col || abs(col - columns[i]) == abs(columns.size() - i)) return false; } return true; } int queensAnswer(int n,vector<int> &columns,int num)//此处的num指的就是当前遍历的第多少行 { if(num == n) return 1;//执行到此步说明已经当前某个皇后布局符合答案且走到了最后一行,方案数+1 int total= 0; for(int i = 0;i < n;i++) { if(can_put(columns,i)) { columns.push_back(i); total += queensAnswer(n,columns,num+1);//即按照当前的各行皇后位置布局其后几行的皇后布局方案数,num+1表示对num行之后的方案搜索 columns.pop_back(); } } return total; } int totalNQueens(int n) { vector<int> columns;//放置各行皇后所在的列 if(n == 1) return 1; else if(n == 2 || n == 3) return 0; else if(n == 4) return 2; else return queensAnswer(n,columns,0); } };