zoukankan      html  css  js  c++  java
  • 回溯法——n后问题

    问题描述:

      在n*n的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n*n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线。

    盲目的迭代枚举:

     1 /*
     2  *作者:xymaqingxiang
     3  *说明:八皇后——盲目迭代法
     4  *分析:通过8重循环模拟搜索空间中的8^8个状态,从中找出满足约束条件的可行性方案
     5  *日期:2014-05-15
     6  */
     7 #include <iostream>
     8 #include <cstdlib>
     9 using namespace std;
    10 bool place(int a[],int n)
    11 {
    12 for(int i=2;i<=n;i++)
    13 {
    14     for(int j=1;j<=i-1;j++)
    15     {
    16         if ((a[i]==a[j])||(abs(a[i]-a[j])==i-j))//不同列和不同斜线约束
    17         {
    18             return false;//位置冲突
    19         }
    20     }
    21 }
    22 return true;//不冲突
    23 }
    24 
    25 void queens()
    26 {
    27     int a[9];
    28     int count = 0;
    29     //每个皇后都从第一列位置开始判断
    30     for(a[1]=1;a[1]<=8;a[1]++)
    31     {
    32         for(a[2]=1;a[2]<=8;a[2]++)
    33         {
    34             for(a[3]=1;a[3]<=8;a[3]++)
    35             {
    36                 for(a[4]=1;a[4]<=8;a[4]++)
    37                 {
    38                     for(a[5]=1;a[5]<=8;a[5]++)
    39                     {
    40                         for(a[6]=1;a[6]<=8;a[6]++)
    41                         {
    42                             for(a[7]=1;a[7]<=8;a[7]++)
    43                             {
    44                                 for(a[8]=1;a[8]<=8;a[8]++)
    45                                 {
    46                                     if(!place(a,8))   //判断位置的合法性
    47                                         continue;
    48                                     else
    49                                     {
    50                                         for(int i=1;i<=8;i++)  
    51                                         {
    52                                             cout<<a[i];        //打印满足约束的可行性放置方案
    53                                         }
    54                                         cout<<endl;
    55                                         count++;
    56                                     }
    57                                 }
    58                             }
    59                         }
    60                     }
    61                 }
    62             }
    63 
    64         }
    65     }
    66     cout<<count<<endl;
    67 }
    68 
    69 void main()
    70 {
    71     queens();
    72     system("pause");
    73 }
    盲目枚举——nQueen
     1 /*
     2  *作者:xymaqingxiang
     3  *说明:八皇后——盲目迭代法
     4  *分析:通过8重循环模拟搜索空间中的8^8个状态,从中找出满足约束条件的可行性方案,但是这样会走很多冤枉路,而回溯的思想就是——走不通则掉头
     5         所以在下面的算法实现中,每进行一个皇后位置的讨论就立刻进行位置的检查,不满足则结束本路径,回头走下一路径,这样便能减少很多冤枉路。
     6         本算法虽可读性良好,但仍局限于解决8皇后问题,对于解决任何N皇后问题还要修改程序结构,不能把皇后个数n作为参数传递给函数,不具有普遍性。
     7         而且程序中出现了大量的for循环,而且for循环的结构很相似,自然让我们想到迭代回溯,即下面我们要讨论的递归回溯和非递归回溯
     8  *日期:2014-05-15
     9  */
    10 #include <iostream>
    11 #include <cstdlib>
    12 using namespace std;
    13 bool place(int a[ ],int n)
    14 {//多次被调用,只需一重循环 
    15     for(int i=1;i<=n-1;i++)
    16     {
    17         if((abs(a[i]-a[n])==n-i)||(a[i]==a[n]))
    18             return false;
    19     }      
    20     return true;
    21 }
    22 
    23 void queens()
    24 {
    25     int a[9];
    26     int count = 0;
    27     for(a[1]=1;a[1]<=8;a[1]++)
    28     {
    29         for(a[2]=1;a[2]<=8;a[2]++)
    30         {
    31             if (!place(a,2))  continue;
    32             for(a[3]=1;a[3]<=8;a[3]++)
    33             {
    34                 if (!place(a,3))  continue;
    35                 for(a[4]=1;a[4]<=8;a[4]++)
    36                 {
    37                     if (!place(a,4))  continue;
    38                     for(a[5]=1;a[5]<=8;a[5]++)
    39                     {
    40                         if (!place(a,5))  continue;
    41                         for(a[6]=1;a[6]<=8;a[6]++)
    42                         {
    43                             if (!place(a,6))  continue;
    44                             for(a[7]=1;a[7]<=8;a[7]++)
    45                             {
    46                                 if (!place(a,7))  continue;
    47                                 for(a[8]=1;a[8]<=8;a[8]++)
    48                                 {
    49                                     if (!place(a,8))  
    50                                         continue;
    51                                     else
    52                                     {
    53                                         for(int i=1;i<=8;i++)  
    54                                         {
    55                                             cout<<a[i];
    56                                         }
    57                                         cout<<endl;
    58                                         count++;
    59                                     }
    60                                 }
    61                             }
    62                         }
    63                     }
    64                 }
    65             }
    66 
    67         }
    68     }
    69     cout<<count<<endl;
    70 }
    71 
    72 void main()
    73 {
    74     queens();
    75     system("pause");
    76 }
    改进版盲目枚举——nQueen

      以上两种算法虽可读性良好,但仍局限于解决8皇后问题,对于解决任何N皇后问题还要修改程序结构,不能把皇后个数n作为参数传递给函数,不具有普遍性。而且程序中出现了大量的for循环,而且for循环的结构很相似,自然让我们想到迭代回溯,即下面我们要讨论的递归回溯和非递归回溯。

    算法分析:

      用n元组x[1:n]表示n后问题的解,其中x[i]表示皇后 i 放在棋盘的第 i 行的第x[i]列

      不同行:事先规定好 i 号皇后只能放置在 i 行上,这样就解决了同行的问题。

      不同列:解向量中的x[i]互不相同,即x[i]!=x[j],如此便实现了不同列的限制。

      不同斜线:最终任意两个皇后的位置(i,x[i])和(j,x[j])不在同一斜线,即斜率的绝对值不能为1,也就是|j-i|!=|x[j]-x[i]|。

      用回溯法解决n后问题时,用完全n叉树表示解空间,可行性约束Place减去不满足行、列和斜线约束的子树。

      下面具体解n后问题的回溯法中,递归函数Backtrack(1)实现对整个解空间的回溯搜索。Backtrack(i)搜索解空间中的第 i 层子树。类Queen的数据成员记录解空间结点信息,以减少传给Backtrack的参数。sum记录当前已找到的可行方案数。

      在算法Backtrack中:

        1、 当i>n时,算法搜索至叶节点,得到一个新的n皇后互不攻击放置方案,当前已找到的可行方案数sum加1。

        2 、当i<=n时,当前扩展结点Z是解空间中的内部结点。该结点有x[i]=1,2,3....n共n个儿子结点。对当前扩展结点Z的每一个儿子结点,由Place检察其可行性,并以深度优先的方式递归地对可行子树搜索,或剪去不可行子树。

    算法描述1:————子集树(仅仅隐藏掉行约束)——规定好行位置,列位置从第一列开始讨论

     1 #include <iostream>
     2 #include <cstdlib>
     3 using namespace std;
     4 class Queen{
     5     friend int nQueen(int);
     6 private:
     7     bool Place(int k);
     8     void Backtrack(int t);
     9     int n,    //皇后个数
    10         * x;   //当前解空间
    11     long sum;   //当前已找到的可行方案数
    12 };
    13 bool Queen::Place(int k)  //位置检查,满足约束则返回true,否则返回false
    14 {
    15     for(int j=1;j<k;j++)    //检查k个皇后不同列和不同斜线的约束语句
    16         if((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k]))
    17             return false;
    18     return true;
    19 }
    20 void Queen::Backtrack(int t)
    21 {
    22     if(t>n)
    23         sum++;  //搜索至叶结点,即讨论完最后一个皇后的位置,得到一个新的不受攻击放置方案,可行方案数加 1
    24     else
    25         for(int i=1;i<=n;i++)
    26         {
    27             x[t] = i;        //确定第 i 个皇后的列位置
    28             if(Place(t))      //检查放置位置是否满足约束条件
    29                 Backtrack(t+1);  //深度优先搜索可行子树
    30         }
    31 }
    32 int nQueen(int n)
    33 {
    34     Queen X;  //定义并初始化X的信息
    35     X.n = n;
    36     X.sum = 0;
    37     int *p = new int [n+1];
    38     for(int i=0;i<=n;i++)
    39         p[i] = 0;
    40     X.x = p;
    41     X.Backtrack(1);
    42     delete [] p;
    43     cout<<X.sum<<endl;
    44     return X.sum;
    45 }
    46 int main()
    47 {
    48     nQueen(4);
    49     nQueen(2);
    50     nQueen(3);
    51     return 0;
    52 }

    迭代回溯:

      数组x 记录了解空间树中从根到当前扩展结点的路径,这些信息已包含了回溯法在回溯时所需要的信息。利用数组x 所含的信息,可将上述回溯法表示成非递归形式,进一步省去O(n)递归栈空间。

    具体的算法描述为:

     1 #include <iostream>
     2 #include <cstdlib>
     3 using namespace std;
     4 class Queen{
     5     friend int nQueen(int);
     6 private:
     7     bool Place(int k);
     8     void Backtrack(void);//.........
     9     int n,
    10         * x;
    11     long sum;
    12 };
    13 bool Queen::Place(int k)  //位置检查函数,满足约束返回true,不满足返回false
    14 {
    15     for(int j=1;j<k;j++)
    16         if( ( abs(k-j) == abs(x[j]-x[k]) ) ||( x[j] == x[k] ) )
    17             return false;
    18     return true;
    19 }
    20 void Queen::Backtrack(void)//非递归实现
    21 {
    22     x[1] = 0;  //x[1]赋初值为0
    23     int k = 1;  //从第一个皇后开始讨论
    24     while(k>0)
    25     {
    26         x[k]+=1;  //x[1]赋初值为0,加1后,表示首先从第一列开始放
    27         while( (x[k]<=n) && !(Place(k)) )//k还不是最后的叶子结点,且位置没有冲突,满足Place函数的约束————x[k]代表列的位置,没有超出列的限制
    28             x[k] += 1;  //在没有超出列的限制且不满足Place函数位置约束时,列后移
    29         if(x[k] <= n)  //没有超出列的限制范围
    30             if(k == n)    //k是叶子结点,已经讨论到最后一个皇后
    31                 sum++;  //可行方案数加1
    32             else
    33             {  //未到达叶子节点(最后一个皇后),且已经为当前的皇后找到合适的位置,k++后处理下一个皇后的放置位置
    34                 k++;    //考虑下一个皇后的放置位置
    35                 x[k] = 0;  //初始位置之前赋值为0
    36             }
    37         else
    38             k--;  //由于已经超出列的限制范围,且当前正考虑的皇后没有找到合适的位置,则前一个皇后的位置后移,重新来过
    39     }
    40 }
    41 int nQueen(int n)
    42 {
    43     Queen X;
    44     X.n = n;
    45     X.sum = 0;
    46     int *p = new int [n+1];
    47     for(int i=0;i<=n;i++)
    48         p[i] = 0;
    49     X.x = p;
    50     X.Backtrack();//......
    51     delete [] p;
    52     cout<<X.sum<<endl;
    53     return X.sum;
    54 }
    55 int main()
    56 {
    57     nQueen(4);
    58     nQueen(2);
    59     nQueen(3);
    60     return 0;
    61 }

     算法描述2:————排列树(隐藏掉行和列约束)——>规定好行位置的同时,列位置从j(不同于上一个皇后的列)开始讨论

     1 #include <iostream>
     2 #include <cstdlib>
     3 using namespace std;
     4 class Queen{
     5     friend int nQueen(int);
     6 private:
     7     bool Place(int k);
     8     void swap(int t,int i,int *x);
     9     void Backtrack(int t);
    10     int n, //皇后个数
    11         * x;//当前解空间
    12     long sum;//当前已找到的可行方案数
    13 };
    14 bool Queen::Place(int k)//位置检查,满足约束则返回true,否则返回false
    15 {
    16     for(int j=1;j<k;j++)//检查k个皇后不同列和不同斜线的约束语句
    17         if((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k]))
    18             return false;
    19     return true;
    20 }
    21 void Queen::swap(int t,int i,int *x)
    22 {
    23     int k;
    24     k=x[t];
    25     x[t]=x[i];
    26     x[i]=k;
    27 }
    28 
    29 void Queen::Backtrack(int t)
    30 {
    31     if(t>n)
    32     {
    33         for(int i=1;i<=n;i++)  //方案数加1前,先打印符合要求的排列组合
    34             cout<<x[i];
    35         cout<<endl;
    36         sum++;//搜索至叶结点,即讨论完最后一个皇后的位置,得到一个新的不受攻击放置方案,可行方案数加 1
    37     }
    38     else
    39         for(int i=t;i<=n;i++)//控制分支数目,每次都要减一,初始值从t开始而非1
    40         {
    41             swap(t,i,x);//交换位置,得到一种新的排列————轮岗ing
    42             if(Place(t))//检查放置位置是否满足约束条件
    43                 Backtrack(t+1);//深度优先搜索可行子树
    44             swap(t,i,x);//调回原位置(初始排列组合)———— 众神归位
    45         }
    46 }
    47 int nQueen(int n)
    48 {
    49     Queen X;//定义并初始化X的信息
    50     X.n = n;
    51     X.sum = 0;
    52     int *p = new int [n+1];
    53     for(int i=1;i<=n;i++)
    54         p[i] = i;  //给x[i]赋初值,必须是其排列的一种
    55     X.x = p;
    56     X.Backtrack(1);
    57     cout<<X.sum<<endl;
    58     delete [] p;
    59     return X.sum;
    60 }
    61 int main()
    62 {
    63     nQueen(4);
    64     nQueen(2);
    65     nQueen(3);
    66     system("pause");
    67     return 0;
    68 }
  • 相关阅读:
    系统分析与设计——作业9
    系统分析与设计——作业8
    系统分析与设计——作业7
    系统分析与设计——作业6
    系统分析与设计——作业5
    系统分析与设计——作业4
    从循环添加事件谈起对JS闭包的理解
    对JS prototype的理解
    系统分析与设计——作业2
    JavaScript 预编译(变量提升和函数提升的原理)
  • 原文地址:https://www.cnblogs.com/xymqx/p/3731031.html
Copyright © 2011-2022 走看看