首页 > 代码库 > 回溯法——n后问题

回溯法——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 }