皇后问题

八皇后问题可谓是算法中的经典问题了,即在8*8的棋盘上摆上8个皇后,这8个皇后的位置之间不能发生冲突,求出有多少种摆法。所谓冲突,指的是在同一条线上,即不能同行、不能同列、不能同一斜线。当然,本博客中,我们也并不限制棋盘的大小,问题也可以扩展为在N*N的棋盘中放置N个皇后的问题,只是以八皇后来说明问题。本随笔中,我们讨论三种解法,常规递归、非递归和位运算递归。

1. 常规递归

分析:很容易,我们能发现该问题能用递归思路解。在摆好七个皇后之后,再放置第八个皇后,但第八个皇后的位置不那么好确定,可能与之前七个皇后的位置发生冲突,也可能不冲突。因此,我们需要采取回溯的方法来不断试探当前皇后的位置,如果位置合法,继续探寻下一个皇后的位置;如果不合法,则需往回探寻其他可能的位置。 当然,在某一次成功探寻完成之后,我们也同样需要回溯来寻找其他可能的解。所以,注定要回溯。先上代码:

 1 const int N = 8;
 2 vector<int> queens(N, -1);//only need a vector to record the columns, for the rows can be represented by the indexes of the vector
 3 int nums = 0;
 4 void printQueens1();//print the chessboard
 5 void printQueens2();//pring the column sequence
 6 void eightQueen1(int r){
 7   if(r == N){//if there is no collision until the ninth queen, the exiting eight queens are valid
 8     nums++;
 9     printQueens2();
10     return;
11   }
12   for(int i=0; i<N; i++){//try all the N columns
13     queens[r] = i;//try to put the current queen in (r, i)
14     bool isCollide = false;
15     for(int j=0; j<r; j++){//check the postition with the existing queens
16       if(queens[j]==queens[r] || queens[j]-queens[r]==j-r || queens[r]-queens[j] == j-r){//is a valid position?
17           isCollide = true;
18           break;
19       }
20     }
21     if(!isCollide){
22       eightQueen1(r+1);
23     }
24   }
25 }
26 void printQueens1(){//print the chessboard
27   for(int i=0; i<N; i++){
28     for(int j=0; j<N; j++){
29       if(queens[i] == j){
30         cout<<"1 ";
31       }else{
32         cout<<"0 ";
33       }
34     }
35     cout<<endl;
36   }
37   cout<<endl;
38 }
39 void printQueens2(){//print the number sequence
40   for(int i=0; i<N; i++){
41     cout<<queens[i];
42   }
43   cout<<endl;
44 }

  代码的关键步骤在算法中已经给出,只是额外说明一点,有的问题需要按照列号序列从小到大的顺序求解特定次序的列号序列,例如,已知八皇后问题有92种解,那么,第55种解是哪一种。上诉算法即是按照列号序列从小到大输出的,用printQueen2()可以很方便地打印任意列号序列。

2. 非递归

  递归的解法是很容易理解的,利用非递归,关键在于抓住回溯的时机,回溯的时机有两个:1. 已经无法为当前皇后找到合适的位置;2. 成功发现一组解后。对于第一种情况,显然我们需要回溯,对于第二种情况,回溯的目的是为了寻找下一组可能解。先上代码:

 1 void eightQueens2(){
 2   int i=0, j=0;
 3   while(i<N){
 4     while(j<N){//find a valid column for row i
 5       if(!isCollide(i, j)){// if find a valid column for row i
 6         queens[i] = j;
 7         j=0;
 8         break;
 9       }else{
10         j++;
11       }
12     }
13     if(queens[i] == -1){//if cannot find a valid column for i, then we need to backtrack
14       if(i == 0){//if cannot find a valid column for the first row, then searching is over
15         break;
16       }else{
17         i--;//backtrack the (i-1)th row
18         j = queens[i]+1;// because the column queens[i] is not valid, then search the next column queens[i]
19         queens[i] = -1;//restore the initial state
20         continue;
21       }
22     }
23     if(i == N-1){//find all valid columns for  0th to (N-1)th row
24       nums++;
25       //printQueens2();
26       j = queens[i] + 1;// backtrack to find the next valid column
27       queens[i] = -1;// restore the initial state
28       continue;
29     }
30     i++;
31   }
32 }

3. 位运算递归

  通过位运算递归非常巧妙,先上算法,然后我们再逐行解析。

 1 int maxOnes = (1<<N)-1;
 2 int r=0;
 3 void putQueen(int r, int lastOne);
 4 void eightQueens3(int row, int ld, int rd){
 5   if(row != maxOnes){
 6     int pos = maxOnes & (~(row | ld | rd));
 7     while(pos){
 8       int lastOne = pos & (~pos+1);
 9       pos -= lastOne;
10       putQueen(r++, lastOne);
11       eightQueens3(row|lastOne, (ld|lastOne) << 1, (rd|lastOne) >> 1);
12       r--;
13     }
14   }else{
15     printQueens2();
16     nums++;
17   }
18 }
19 void putQueen(int r, int lastOne){
20     int c = 0;
21     while(lastOne/2){
22         c++;
23         lastOne /= 2;
24     }
25     queens[r] = c;
26 }

  对于第一行,maxOnes = (1<<N)-1 代表将maxOnes的后N位全置为1,剩余为为0。

  对于第二行,r表示行号,用于打印棋盘;

  第三行,打印棋盘;

  第四行,row中的后N bits对应棋盘上的N列,在理解了算法1后,自然也能理解;而ld和rd分别代表了主对角线(及其平行线)和副对角线(及其平行线)上的放置情况,其初始值均为0。其中,row中记录了已经放置的列,ld和rd中则放置了下一步不能放置的列。

  第五行,row从0开始,当row中放满N个bit之后,说明N个皇后已经放置好,如果没有则继续递归寻找。

  第六行,maxOnes & ~(row | ld | rd)是为了找出下一行中的可以放置的列,若pos的对应bit为1,则代表该列还可以继续放置皇后。

  第七行,查找开始,pos中有多少个bit位为1,则代表这些bit位是可以放置的,因此应该要探寻完这些合理的bit位。

  第八行,pos & (~pos+1)代表取pos中的最后一个1,也可以用pos & -pos来表达;

  第九行,当选出最后一个1后,为了下一次的循环,应该将其删除,以寻找下一个1;

  第十行,放置第r个皇后;

  第十一行,递归调用,row|lastOne代表将lastOne列放置皇后;(ld|lastOne) << 1代表这新加入的这个皇后的左对角线不能再放置皇后(例如,当前皇后位置为(r,queens[r])时,下一个皇后决不能出现在其左对角线上,即(r+1,queens[r]-1),因此需要将对应列设为禁位,这样便能在第四行时正确得到第r+1行时可以放置的位置;同样,(rd|lastOne) >> 1,代表新加入的这个皇后的右对角线上(r+1,queens[r]+1)不能放置皇后,同样需将对应列设禁位。

  第十二行,回溯到前一个皇后

发现:以上三个算法的时间复逐渐递减,位运算递归算法是最快的,特别是针对皇后较多的情况(如N>14),会发现其运行速度要比其他算法要快很多。

参考:

http://blog.csdn.net/hackbuteer1/article/details/6657109

原文地址:https://www.cnblogs.com/wuiCoding/p/6733426.html