下面就八皇后问题做一个简单的从规则到问题提取过程:
(1)因为所有的皇后都不能放在同一列/同一行,因此数组的不能存在相同的两个值。
(2)所有的皇后都不能在对角线上,那么该如何检测两个皇后是否在同一个对角线上?我们将棋盘的方格成一个二维数组,如下:
假设有两个皇后被放置在(i,j)和(k,l)的位置上,明显,当且仅当|i-k|=|j-l| 时,两个皇后才在同一条对角线上。
回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:
* 找到一个可能存在的正确的答案
* 在尝试了所有可能的分步方法后宣告该问题没有答案
在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。
回溯的思想是:假设某一行为当前状态,不断检查该行所有的位置是否能放一个皇后,检索的状态有两种:
(1)先从首位开始检查,如果不能放置,接着检查该行第二个位置,依次检查下去,直到在该行找到一个可以放置一个皇后的地方,然后保存当前状态,转到下一行重复上述方法的检索。
(2)如果检查了该行所有的位置均不能放置一个皇后,说明上一行皇后放置的位置无法让所有的皇后找到自己合适的位置,因此就要回溯到上一行,重新检查该皇后位置后面的位置。
如果我们用一个数组来保存当前的状态,上面的检索过程是否有点像堆栈的操作?如果找到可行位置,压栈,如果当前行所有位置不行,将出栈。好了,问题模型逐渐清晰开来了,我们可以定义一个过程,这个过程负责检索的过程,如果检索到当前行某个位置可行,压栈,如果当前行所有位置不行,将执行出栈操作。8皇后问题,我们假定栈的大小为8,如果栈满了,表示找到了可行方法,将执行所有出栈操作。
- * 回溯法解N皇后问题
- * 使用一个一维数组表示皇后的位置
- * 其中数组的下标表示皇后所在的行
- * 数组元素的值表示皇后所在的列
- * 这样设计的棋盘,所有皇后必定不在同一行
- *
- * 假设前n-1行的皇后已经按照规则排列好
- * 那么可以使用回溯法逐个试出第n行皇后的合法位置
- * 所有皇后的初始位置都是第0列
- * 那么逐个尝试就是从0试到N-1
- * 如果达到N,仍未找到合法位置
- * 那么就置当前行的皇后的位置为初始位置0
- * 然后回退一行,且该行的皇后的位置加1,继续尝试
- * 如果目前处于第0行,还要再回退,说明此问题已再无解
- *
- * 如果当前行的皇后的位置还是在0到N-1的合法范围内
- * 那么首先要判断该行的皇后是否与前几行的皇后互相冲突
- * 如果冲突,该行的皇后的位置加1,继续尝试
- * 如果不冲突,判断下一行的皇后
- * 如果已经是最后一行,说明已经找到一个解,输出这个解
- * 然后最后一行的皇后的位置加1,继续尝试下一个解
- */
- #define MAX_LENGTH 1024
- /*
- * 检查第n行的皇后与前n-1行的皇后是否有冲突
- * 发生冲突的充分必要条件是:
- * a) 两皇后处于同一列,即a[i] == a[n]
- * b) 两皇后处于同一斜线,即|a[i] – a[n]| == |i – n| == n – i
- */
- int is_conflict(int *a, int n) {
- int flag = 0;
- int i;
- for ( i = 0; i < n; i++ ) {
- if ( a[i] == a[n] || a[i] – a[n] == n – i || a[n] – a[i] == n – i ) {
- flag = 1;
- break;
- }
- }
- return flag;
- }
- /*
- * 输出皇后的排列
- */
- void print_board(int *a, int n) {
- int i, j;
- for ( i = 0; i < n; i++ ) {
- for ( j = 0; j < a[i]; j++ ) {
- printf(” “);
- }
- printf(“Q”);
- for ( j = a[i] + 1; j < n; j++ ) {
- printf(” “);
- }
- printf(“/n”);
- }
- printf(“——–/n”);
- }
- /*
- * 初始化棋盘,所有皇后都在第0列
- */
- void init_board(int *a, int n) {
- int i;
- for ( i = 0; i < n; i++ ) {
- a[i] = 0;
- }
- }
- /*
- * 解决N皇后问题
- */
- int queen(int n) {
- int count = 0;
- int a[MAX_LENGTH];
- init_board(a, n);
- int i = 0;
- while ( 1 ) {
- if ( a[i] < n ) {
- // 如果皇后的位置尚未超出棋盘范围
- // 需要检查第i行的皇后是否与前i-1行的皇后冲突
- if ( is_conflict(a, i) ) {
- // 如果冲突,尝试下一个位置
- a[i]++;
- continue;
- }
- if ( i >= n – 1 ) {
- // 如果已经到最后一行,也即找到一个解,首先输出它
- count++;
- print_board(a, n);
- // 然后尝试当前行的皇后的下一个位置
- a[n-1]++;
- continue;
- }
- // 没有冲突,尝试下一行
- i++;
- continue;
- }
- else {
- // 皇后的位置已经超出棋盘范围
- // 那么该行的皇后复位
- a[i] = 0;
- // 回退到上一行
- i–;
- if ( i < 0 ) {
- // 已经不能再退了,函数结束
- return count;
- }
- // 尝试上一行的皇后的下个位置
- a[i]++;
- continue;
- }
- }
- }
- int main(void) {
- int n = 8;
- int count = queen(n);
- printf(“%d solutions in %d queens problem/n”, count, n);
- return 0;
- }
- * 回溯法解N皇后问题
- * 使用一个一维数组表示皇后的位置
- * 其中数组的下标表示皇后所在的行
- * 数组元素的值表示皇后所在的列
- * 这样设计的棋盘,所有皇后必定不在同一行
- *
- * 假设前n-1行的皇后已经按照规则排列好
- * 那么可以使用回溯法逐个试出第n行皇后的合法位置
- * 所有皇后的初始位置都是第0列
- * 那么逐个尝试就是从0试到N-1
- * 如果达到N,仍未找到合法位置
- * 那么就置当前行的皇后的位置为初始位置0
- * 然后回退一行,且该行的皇后的位置加1,继续尝试
- * 如果目前处于第0行,还要再回退,说明此问题已再无解
- *
- * 如果当前行的皇后的位置还是在0到N-1的合法范围内
- * 那么首先要判断该行的皇后是否与前几行的皇后互相冲突
- * 如果冲突,该行的皇后的位置加1,继续尝试
- * 如果不冲突,判断下一行的皇后
- * 如果已经是最后一行,说明已经找到一个解,输出这个解
- * 然后最后一行的皇后的位置加1,继续尝试下一个解
- */
- #define MAX_LENGTH 1024
- /*
- * 检查第n行的皇后与前n-1行的皇后是否有冲突
- * 发生冲突的充分必要条件是:
- * a) 两皇后处于同一列,即a[i] == a[n]
- * b) 两皇后处于同一斜线,即|a[i] – a[n]| == |i – n| == n – i
- */
- int is_conflict(int *a, int n) {
- int flag = 0;
- int i;
- for ( i = 0; i < n; i++ ) {
- if ( a[i] == a[n] || a[i] – a[n] == n – i || a[n] – a[i] == n – i ) {
- flag = 1;
- break;
- }
- }
- return flag;
- }
- /*
- * 输出皇后的排列
- */
- void print_board(int *a, int n) {
- int i, j;
- for ( i = 0; i < n; i++ ) {
- for ( j = 0; j < a[i]; j++ ) {
- printf(” “);
- }
- printf(“Q”);
- for ( j = a[i] + 1; j < n; j++ ) {
- printf(” “);
- }
- printf(“/n”);
- }
- printf(“——–/n”);
- }
- /*
- * 初始化棋盘,所有皇后都在第0列
- */
- void init_board(int *a, int n) {
- int i;
- for ( i = 0; i < n; i++ ) {
- a[i] = 0;
- }
- }
- /*
- * 解决N皇后问题
- */
- int queen(int n) {
- int count = 0;
- int a[MAX_LENGTH];
- init_board(a, n);
- int i = 0;
- while ( 1 ) {
- if ( a[i] < n ) {
- // 如果皇后的位置尚未超出棋盘范围
- // 需要检查第i行的皇后是否与前i-1行的皇后冲突
- if ( is_conflict(a, i) ) {
- // 如果冲突,尝试下一个位置
- a[i]++;
- continue;
- }
- if ( i >= n – 1 ) {
- // 如果已经到最后一行,也即找到一个解,首先输出它
- count++;
- print_board(a, n);
- // 然后尝试当前行的皇后的下一个位置
- a[n-1]++;
- continue;
- }
- // 没有冲突,尝试下一行
- i++;
- continue;
- }
- else {
- // 皇后的位置已经超出棋盘范围
- // 那么该行的皇后复位
- a[i] = 0;
- // 回退到上一行
- i–;
- if ( i < 0 ) {
- // 已经不能再退了,函数结束
- return count;
- }
- // 尝试上一行的皇后的下个位置
- a[i]++;
- continue;
- }
- }
- }
- int main(void) {
- int n = 8;
- int count = queen(n);
- printf(“%d solutions in %d queens problem/n”, count, n);
- return 0;
- }