n皇后 - 位运算版

 n皇后问题是大家在递归里会碰到的一个经典问题。以前高中我学DFS的时候,老师首先让我看的就是八皇后。

不过这皇后的时间复杂度大家可想而知了。而接下来的位运算将这个效率重新提到一个高度。

我是以前在Matrix67大牛那里学的,最近数据结构实验刚好碰到n皇后,就在这里“复述”一遍吧。

Code:
  1. void doans(int r, int ld, int rd)  
  2. {  
  3.     if(r != uplimit)  
  4.     {  
  5.         int pos = uplimit & ~(r | ld | rd);  
  6.   
  7.         while(pos != 0)  
  8.         {  
  9.             int p = pos & (~pos + 1);  
  10.             pos -= p;  
  11.             doans(r + p, (ld + p) << 1, (rd + p) >> 1);  
  12.         }  
  13.     }  
  14.     else sum++;  
  15. }  

乍一看有点模糊吧?没错,这就是n皇后的递归函数了。我们一个个来解析。

首先uplimit是(1 << n) - 1,如果n是8的话uplimit就是255,看做二进制就是11111111。聪明的人一看就知道了,这里每一位就代表一个皇后。

而r代表每一列能放与否,如10001110就代表第2、3、4、8个能放。

所以开始一个if来判断皇后放齐了没。如果齐了,显然r也要等于11111111。

还有ld和rd分别是对角线的各位能放与否。

我们来看看下图(From Matrix67):

  

假设我们已经递归到第三行了(左图),这里可以看出r为101010也就是说二、四、六可以放。ld是100100(蓝色线),二、三、五、六可以放,rd为000111,。

好的,那么哪几个可以放呢?我们将r、ld、rd或运算一遍(或者r有或者ld有或者rd有),得到101111,也就是说这三个合起来的话就只能放第二格了。然后我非运算一遍,得到的是010000,非运算之后1代表可以放,0代表不可以放了。然后我们再与运算一遍就得到可以放的位置了:010000。

下一步,如果能放的位置不是0的话我们就开始放:

Code:
  1. int p = pos & (~pos + 1);  

这一句我们可以用

Code:
  1. int p = pos & -pos;  

来代替,什么意思呢?其结果是取出最右边的那个1。比如我们有一个数字pos是10010,那么p得到的结果就是10了。那么上面那个010000得到的结果就是10000了。然后我们更新一遍pos:让它减去p也就是10000,那么pos得到的结果就是000000。也就是说下一次循环就跳出了。

好的,我们不管下一次循环,我们讲怎么进入下一层递归:

看看传进去的三个参数:

r + p为下一层递归的r,即10000 + 101010 = 111010那就相当于放进第二个了。

(ld + p) << 1为下一层的ld,即(100100 + 10000) << 1为(110100) << 1也就是1101000了。当然,在下一层递归的时候第一个1将会被uplimit & ~(r | ld | rd)截掉成为101000。

(rd + p) >> 1为下一层的rd,即(000111 + 10000) >> 1为(010111) >> 1也就是001011。

那么在新的一层里又有新的r和ld还有rd组合了。

主题的思路就是这样子的。

 

最后方便大家理解学习,发下我的n皇后代码:

Code:
  1. /** 
  2.  * @brief n皇后问题 - 位运算版 
  3.  * @Author 朱凯迪 
  4.  * 2010/11/22 
  5.  */  
  6. #include   
  7. #include   
  8. using namespace std;  
  9.   
  10. /** 方案链表 */  
  11. list<int> sol;  
  12.   
  13. /** 棋盘大小 */  
  14. int n;  
  15. /** 棋盘摆满时的数字 */  
  16. int uplimit;  
  17.   
  18. void print()  
  19. {  
  20.     printf("/n");  
  21.     list<int>::iterator i;  
  22.     for(i = sol.begin(); i != sol.end(); i++)  
  23.     {  
  24.         int tmp = *i, cnt = 0;  
  25.         while(tmp != 1)  
  26.         {  
  27.             cnt++;  
  28.             tmp = tmp >> 1;  
  29.         }  
  30.         for(int j = 0; j < cnt; j++) printf("■");  
  31.         printf("○");  
  32.         for(int j = cnt + 1; j < n; j++) printf("■");  
  33.         printf("/n");  
  34.     }  
  35.     printf("/n");  
  36. }  
  37.   
  38. void doans(int r, int ld, int rd)  
  39. {  
  40.     if(r != uplimit)  
  41.     {  
  42.         /** 还没摆满 */  
  43.   
  44.         /** 取能放的位置 */  
  45.         int pos = uplimit & ~(r | ld | rd);  
  46.   
  47.         /** 若还有位置如00001011表示能放5、7、8位 */  
  48.         while(pos != 0)  
  49.         {  
  50.             /** 取低位 */  
  51.             int p = pos & (~pos + 1);  
  52.   
  53.             /** 更新位置 */  
  54.             pos -= p;  
  55.   
  56.             /** 答案入 */  
  57.             sol.push_back(p);  
  58.   
  59.             /** 更新禁手并进行下一层更新 */  
  60.             doans(r + p, (ld + p) << 1, (rd + p) >> 1);  
  61.   
  62.             /** 答案出 */  
  63.             sol.pop_back();  
  64.         }  
  65.     }  
  66.     else print();  
  67. }  
  68.   
  69. int main()  
  70. {  
  71.     while(cin >> n)  
  72.     {  
  73.         /** 如n为8,则uplimit为11111111 */  
  74.         uplimit = (1 << n) - 1;  
  75.         doans(0, 0, 0);  
  76.     }  
  77.   
  78.     return 0;  
  79. }  

 

 

你可能感兴趣的:(n皇后 - 位运算版)