回溯法——八 / N 皇后问题

ゝ一世哀愁。 2022-04-13 05:39 444阅读 0赞

回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。

回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。

回溯法指导思想——走不通,就掉头。设计过程:确定问题的解空间;确定结点的扩展规则;搜索。

n皇后问题

要在n*n的国际象棋棋盘中放n个皇后,使任意两个皇后都不能互相吃掉。规则:皇后能吃掉同一行、同一列、同一对角线的任意棋子。求所有的解。n=8是就是著名的八皇后问题了。

设八个皇后为xi,分别在第i行(i=1,2,3,4……,8);

问题的解状态:可以用(1,x1),(2,x2),……,(8,x8)表示8个皇后的位置;

由于行号固定,可简单记为:(x1,x2,x3,x4,x5,x6,x7,x8);

问题的解空间:(x1,x2,x3,x4,x5,x6,x7,x8),1≤xi≤8(i=1,2,3,4……,8),共88个状态;

约束条件:八个(1,x1),(2,x2) ,(3,x3),(4,x4) ,(5,x5), (6,x6) , (7,x7), (8,x8)不在同一行、同一列和同一对角线上。

盲目的枚举算法:通过8重循环模拟搜索空间中的88个状态,从中找出满足约束条件的“答案状态”。程序如下:

  1. /*
  2. *说明:八皇后——盲目迭代法
  3. *日期:2013-12-18
  4. */
  5. #include <iostream>
  6. using namespace std;
  7. bool check_1(int a[],int n)
  8. {
  9. for(int i=2;i<=n;i++)
  10. {
  11. for(int j=1;j<=i-1;j++)
  12. {
  13. if ((a[i]==a[j])||(abs(a[i]-a[j])==i-j))
  14. {
  15. return false;
  16. }
  17. }
  18. }
  19. return true;//不冲突
  20. }
  21. void queens_1()
  22. {
  23. int a[9];
  24. int count = 0;
  25. for(a[1]=1;a[1]<=8;a[1]++)
  26. {
  27. for(a[2]=1;a[2]<=8;a[2]++)
  28. {
  29. for(a[3]=1;a[3]<=8;a[3]++)
  30. {
  31. for(a[4]=1;a[4]<=8;a[4]++)
  32. {
  33. for(a[5]=1;a[5]<=8;a[5]++)
  34. {
  35. for(a[6]=1;a[6]<=8;a[6]++)
  36. {
  37. for(a[7]=1;a[7]<=8;a[7]++)
  38. {
  39. for(a[8]=1;a[8]<=8;a[8]++)
  40. {
  41. if(!check_1(a,8))
  42. continue;
  43. else
  44. {
  45. for(int i=1;i<=8;i++)
  46. {
  47. cout<<a[i];
  48. }
  49. cout<<endl;
  50. count++;
  51. }
  52. }
  53. }
  54. }
  55. }
  56. }
  57. }
  58. }
  59. }
  60. cout<<count<<endl;
  61. }
  62. void main()
  63. {
  64. queens_1();
  65. }

程序思想比较简单,最后可知共92种摆放方法。如果能够排除那些没有前途的状态,会节约时间——回溯法(走不通,就回头)。

  1. bool check_2 (int a[ ],int n)
  2. {//多次被调用,只需一重循环
  3. for(int i=1;i<=n-1;i++)
  4. {
  5. if((abs(a[i]-a[n])==n-i)||(a[i]==a[n]))
  6. return false;
  7. }
  8. return true;
  9. }
  10. void queens_2()
  11. {
  12. int a[9];
  13. int count = 0;
  14. for(a[1]=1;a[1]<=8;a[1]++)
  15. {
  16. for(a[2]=1;a[2]<=8;a[2]++)
  17. {
  18. if (!check_2(a,2)) continue;
  19. for(a[3]=1;a[3]<=8;a[3]++)
  20. {
  21. if (!check_2(a,3)) continue;
  22. for(a[4]=1;a[4]<=8;a[4]++)
  23. {
  24. if (!check_2(a,4)) continue;
  25. for(a[5]=1;a[5]<=8;a[5]++)
  26. {
  27. if (!check_2(a,5)) continue;
  28. for(a[6]=1;a[6]<=8;a[6]++)
  29. {
  30. if (!check_2(a,6)) continue;
  31. for(a[7]=1;a[7]<=8;a[7]++)
  32. {
  33. if (!check_2(a,7)) continue;
  34. for(a[8]=1;a[8]<=8;a[8]++)
  35. {
  36. if (!check_2(a,8))
  37. continue;
  38. else
  39. {
  40. for(int i=1;i<=8;i++)
  41. {
  42. cout<<a[i];
  43. }
  44. cout<<endl;
  45. count++;
  46. }
  47. }
  48. }
  49. }
  50. }
  51. }
  52. }
  53. }
  54. }
  55. cout<<count<<endl;
  56. }
  57. void main()
  58. {
  59. queens_2();
  60. }

n此算法可读性很好,体现了“回溯”。但它只针对八皇后问题,解决任意的n皇后问题还要修改程序结构。如果要解决n皇后的问题,就需要将n作为参数传递给函数,函数需要重写来实现回溯(不能采用级联的for循环,n不确定);从另一方面,程序中出现了大量的for循环,而且for中的函数结构很相似,自然想到的是递归迭代回溯。这就是回溯比较常用的两种实现方法:非递归回溯和递归回溯。

非递归回溯的程序实现:

  1. void backdate (int n)
  2. {
  3. int count = 0;
  4. int a[100];
  5. int k = 1;
  6. a[1]=0;
  7. while(k>0)
  8. {
  9. a[k]=a[k]+1;//对应for循环的1~n
  10. while((a[k]<=n)&&(!check_2(a,k)))//搜索第k个皇后位置
  11. {
  12. a[k]=a[k]+1;
  13. }
  14. if(a[k]<=n)//找到了合理的位置
  15. {
  16. if(k==n )
  17. {//找到一组解
  18. for(int i=1;i<=8;i++)
  19. {
  20. cout<<a[i];
  21. }
  22. cout<<endl;
  23. count++;
  24. }
  25. else
  26. {
  27. k=k+1;//继续为第k+1个皇后找到位置,对应下一级for循环
  28. a[k]=0;//下一个皇后一定要从头开始搜索
  29. }
  30. }
  31. else
  32. {
  33. k=k-1;//回溯,对应执行外内层for循环回到更上层
  34. }
  35. }
  36. cout<<count<<endl;
  37. }
  38. void main()
  39. {
  40. backdate(8);
  41. }

这样也可以得到,8皇后问题的92中结果。更简单、可读的方法是采用递归的方式,如下:

  1. int a[100], n, count;
  2. void backtrack(int k)
  3. {
  4. if (k>n)//找到解
  5. {
  6. for(int i=1;i<=8;i++)
  7. {
  8. cout<<a[i];
  9. }
  10. cout<<endl;
  11. count++;
  12. }
  13. else
  14. {
  15. for (int i = 1;i <=n; i++)
  16. {
  17. a[k] = i;
  18. if (check_2(a,k) == 1)
  19. {backtrack(k+1);}
  20. }
  21. }
  22. }
  23. void main()
  24. {
  25. n=8,count=0;
  26. backtrack(1);
  27. cout<<count<<endl;
  28. }

眨眼可见,递归调用大大减少了代码量,也增加了程序的可读性。给出其中的一个解,如下:

image

将这个八皇后扩展到n皇后问题。 下面是java版的代码:

  1. public class N_Queens {
  2. static int n=8 ;
  3. static long sum ;
  4. static int[] x ;
  5. public static void main(String[] args) {
  6. sum =0;
  7. x=new int[n+1];
  8. for (int i = 0; i <=n ; i++) {
  9. x[i]=0;
  10. }
  11. Backtrack(1);
  12. System.out.println(sum);
  13. }
  14. static void Backtrack(int index){
  15. if(index>n){
  16. sum++;
  17. }else{
  18. for(int i=1;i<=n;i++){
  19. x[index]=i;
  20. if(Containt(index)){
  21. Backtrack(index+1);
  22. }
  23. }
  24. }
  25. }
  26. //约束函数
  27. //保证不在同一列上,而且不在统一斜线上, 为什么不去保证不在同一行上,因为默认数组的索引就不是相同的
  28. private static boolean Containt(int k) {//t代表试探皇后的位置
  29. for (int j = 1; j <k ; j++) {//在保证当前皇后位置在[1,t)之间不存在满足同一斜线或者同一列的要求
  30. if((Math.abs(k-j)==Math.abs(x[j]-x[k]))||(x[j]==x[k])){
  31. return false;
  32. }
  33. }
  34. return true;
  35. }
  36. }

发表评论

表情:
评论列表 (有 0 条评论,444人围观)

还没有评论,来说两句吧...

相关阅读

    相关 回溯-N皇后问题

    一、N皇后问题 n皇后问题:要求在一个n×n的棋盘上放置n个皇后,使得任意两个皇后不在同一行或同一列或同一斜线上。 二、回溯法 回溯法是一类非常重要的算法设计方法

    相关 回溯皇后问题

    八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横

    相关 皇后问题(回溯)

    问题描述: 在8\8的棋盘上,放置8个皇后,使他们互相不攻击; 解析: 进行逐行放置,皇后肯定不会进行横向攻击,因此只需检查纵向和斜向是否会进行攻击即可 代码:  C

    相关 皇后问题回溯

    问题描述 ![70][] 在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上(与水平行轴成45°或135°),问有多少

    相关 回溯皇后

    回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再

    相关 回溯—— / N 皇后问题

    回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。 回溯法在问题的解空间树中,按深度优先策略,从根结点