飞道的博客

皇后问题

364人阅读  评论(0)

八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n1×n1,而皇后个数也变成n2。而且仅当 n2 ≥ 1 或 n1 ≥ 4 时问题有解。

皇后问题是非常著名的问题,作为一个棋盘类问题,毫无疑问,用暴力搜索的方法来做是一定可以得到正确答案的,但在有限的运行时间内,我们很难写出速度可以忍受的搜索,部分棋盘问题的最优解不是搜索,而是动态规划,某些棋盘问题也很适合作为状态压缩思想的解释例题。

进一步说,皇后问题可以用人工智能相关算法和遗传算法求解,可以用多线程技术缩短运行时间。本文不做讨论。

(本文不展开讲状态压缩,以后再说)

 

一般思路:

 

N*N的二维数组,在每一个位置进行尝试,在当前位置上判断是否满足放置皇后的条件(这一点的行、列、对角线上,没有皇后)。

 

优化1:

 

既然知道多个皇后不能在同一行,我们何必要在同一行的不同位置放多个来尝试呢?

我们生成一维数组record,record[i]表示第i行的皇后放在了第几列。对于每一行,确定当前record值即可,因为每行只能且必须放一个皇后,放了一个就无需继续尝试。那么对于当前的record[i],查看record[0...i-1]的值,是否有j = record[k](同列)、|record[k] - j| = | k-i |(同一斜线)的情况。由于我们的策略,无需检查行(每行只放一个)。


  
  1. public class NQueens {
  2. public static int num1(int n) {
  3. if (n < 1) {
  4. return 0;
  5. }
  6. int[] record = new int[n];
  7. return process1( 0, record, n);
  8. }
  9. public static int process1(int i, int[] record, int n) {
  10. if (i == n) {
  11. return 1;
  12. }
  13. int res = 0;
  14. for ( int j = 0; j < n; j++) {
  15. if (isValid(record, i, j)) {
  16. record[i] = j;
  17. res += process1(i + 1, record, n);
  18. }
  19. } //对于当前行,依次尝试每列
  20. return res;
  21. }
  22. //判断当前位置是否可以放置
  23. public static boolean isValid(int[] record, int i, int j) {
  24. for ( int k = 0; k < i; k++) {
  25. if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
  26. return false;
  27. }
  28. }
  29. return true;
  30. }
  31. public static void main(String[] args) {
  32. int n = 8;
  33. System.out.println(num1(n));
  34. }
  35. }

优化2:

 

分析:棋子对后续过程的影响范围:本行、本列、左右斜线。

黑色棋子影响区域为红色

本行影响不提,根据优化一已经避免

本列影响,一直影响D列,直到第一行在D放棋子的所有情况结束。

 

左斜线:每向下一行,实际上对当前行的影响区域就向左移动

比如:

尝试第二行时,黑色棋子影响的是我们的第三列;

尝试第三行时,黑色棋子影响的是我们的第二列;

尝试第四行时,黑色棋子影响的是我们的第一列;

尝试第五行及以后几行,黑色棋子对我们并无影响。

 

右斜线则相反:

随着行序号增加,影响的列序号也增加,直到影响的列序号大于8就不再影响。

 

我们对于之前棋子影响的区域,可以用二进制数字来表示,比如:

每一位,用01代表是否影响。

比如上图,对于第一行,就是00010000

尝试第二行时,数字变为00100000

第三行:01000000

第四行:10000000

 

对于右斜线的数字,同理:

第一行00010000,之后向右移:00001000,00000100,00000010,00000001,直到全0不影响。

 

同理,我们对于多行数据,也同样可以记录了

比如在第一行我们放在了第四列:

第二行放在了G列,这时左斜线记录为00100000(第一个棋子的影响)+00000010(当前棋子的影响)=00100010。

到第三行数字继续左移:01000100,然后继续加上我们的选择,如此反复。

 

这样,我们对于当前位置的判断,其实可以通过左斜线变量、右斜线变量、列变量,按位或运算求出(每一位中,三个数有一个是1就不能再放)。

具体看代码:

注:怎么排版就炸了呢。。。贴一张图吧


  
  1. public class NQueens {
  2. public static int num2(int n) {
  3. // 因为本方法中位运算的载体是int型变量,所以该方法只能算1~32皇后问题
  4. // 如果想计算更多的皇后问题,需使用包含更多位的变量
  5. if (n < 1 || n > 32) {
  6. return 0;
  7. }
  8. int upperLim = n == 32 ? - 1 : ( 1 << n) - 1;
  9. //upperLim的作用为棋盘大小,比如8皇后为00000000 00000000 00000000 11111111
  10. //32皇后为11111111 11111111 11111111 11111111
  11. return process2(upperLim, 0, 0, 0);
  12. }
  13. public static int process2(int upperLim, int colLim, int leftDiaLim,
  14. int rightDiaLim) {
  15. if (colLim == upperLim) {
  16. return 1;
  17. }
  18. int pos = 0; //pos:所有的合法位置
  19. int mostRightOne = 0; //所有合法位置的最右位置
  20. //所有记录按位或之后取反,并与全1按位与,得出所有合法位置
  21. pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));
  22. int res = 0; //计数
  23. while (pos != 0) {
  24. mostRightOne = pos & (~pos + 1); //取最右的合法位置
  25. pos = pos - mostRightOne; //去掉本位置并尝试
  26. res += process2(
  27. upperLim, //全局
  28. colLim | mostRightOne, //列记录
  29. //之前列+本位置
  30. (leftDiaLim | mostRightOne) << 1, //左斜线记录
  31. //(左斜线变量+本位置)左移
  32. (rightDiaLim | mostRightOne) >>> 1); //右斜线记录
  33. //(右斜线变量+本位置)右移(高位补零)
  34. }
  35. return res;
  36. }
  37. public static void main(String[] args) {
  38. int n = 8;
  39. System.out.println(num2(n));
  40. }
  41. }

完整测试代码:

32皇后:结果/时间

暴力搜:时间就太长了,懒得测。。。


  
  1. public class NQueens {
  2. public static int num1(int n) {
  3. if (n < 1) {
  4. return 0;
  5. }
  6. int[] record = new int[n];
  7. return process1( 0, record, n);
  8. }
  9. public static int process1(int i, int[] record, int n) {
  10. if (i == n) {
  11. return 1;
  12. }
  13. int res = 0;
  14. for ( int j = 0; j < n; j++) {
  15. if (isValid(record, i, j)) {
  16. record[i] = j;
  17. res += process1(i + 1, record, n);
  18. }
  19. }
  20. return res;
  21. }
  22. public static boolean isValid(int[] record, int i, int j) {
  23. for ( int k = 0; k < i; k++) {
  24. if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
  25. return false;
  26. }
  27. }
  28. return true;
  29. }
  30. public static int num2(int n) {
  31. if (n < 1 || n > 32) {
  32. return 0;
  33. }
  34. int upperLim = n == 32 ? - 1 : ( 1 << n) - 1;
  35. return process2(upperLim, 0, 0, 0);
  36. }
  37. public static int process2(int upperLim, int colLim, int leftDiaLim,
  38. int rightDiaLim) {
  39. if (colLim == upperLim) {
  40. return 1;
  41. }
  42. int pos = 0;
  43. int mostRightOne = 0;
  44. pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));
  45. int res = 0;
  46. while (pos != 0) {
  47. mostRightOne = pos & (~pos + 1);
  48. pos = pos - mostRightOne;
  49. res += process2(upperLim, colLim | mostRightOne,
  50. (leftDiaLim | mostRightOne) << 1,
  51. (rightDiaLim | mostRightOne) >>> 1);
  52. }
  53. return res;
  54. }
  55. public static void main(String[] args) {
  56. int n = 32;
  57. long start = System.currentTimeMillis();
  58. System.out.println(num2(n));
  59. long end = System.currentTimeMillis();
  60. System.out.println( "cost time: " + (end - start) + "ms");
  61. start = System.currentTimeMillis();
  62. System.out.println(num1(n));
  63. end = System.currentTimeMillis();
  64. System.out.println( "cost time: " + (end - start) + "ms");
  65. }
  66. }

 


转载:https://blog.csdn.net/hebtu666/article/details/84631083
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场