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

时间: 2024-10-02 06:11:14

回溯法——n后问题的相关文章

AI -- 回溯法解决四皇后问题

AI -- 回溯法解决四皇后问题 回溯法 四皇后 C语言 问题描述 在 4*4 的棋盘上无冲突的摆放 4 个皇后,无冲突是指一个皇后所在位置的水平.竖直以及斜线上不能出现其他的皇后,其他的 n 皇后问题以此类推 解决方法 按行来摆放棋子,下一行的摆放满足于与上一行的棋子没有冲突,否则就返回上一步走其他的路线,这就是所谓的回溯法 详细说明 在第一行有四种可能,选择第一个位置放上皇后 第二行原本可以有四种可能摆放,但是第一第二个已经和第一行的皇后冲突了,因此只剩下第三第四个格子了,先选择第三个格子

回溯法解决四皇后问题

以4皇后为例,其他的N皇后问题以此类推.所谓4皇后问题就是求解如何在4×4的棋盘上无冲突的摆放4个皇后棋子.在国际象棋中,皇后的移动方式为横竖交叉的,因此在任意一个皇后所在位置的水平.竖直.以及45度斜线上都不能出现皇后的棋子,例子 要求编程求出符合要求的情况的个数.四皇后问题有很多种解法,这里主要介绍一种经典的解决方法:回溯法 回溯法的基本思想是:可以构建出一棵解空间树,通过探索这棵解空间树,可以得到四皇后问题的一种或几种解.这样的解空间树有四棵 在如上图所示的4×4的棋盘上,按列来摆放棋子,

回溯法 -数据结构与算法

1.回溯法算法思想: 定义: 回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标.但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”. 1.回溯法适用:有许多问题,当需要找出它的解集(全部解)或者要求回答什么解是满足某些约束条件的最优解时,往往要使用回溯法. 2.有组织的穷举式搜索:回溯法的基本做法是搜索或者有的组织穷尽搜索.它能避免搜索所有的可能性.即避免不必要的搜索.这种方

回溯法-01背包问题之一:递归模式

一.回溯法 回溯法是一个既带有系统性又带有跳跃性的搜索算法.它在包含问题的所有解的解空间树中按照深度优先的策略,从根节点出发搜索解空间树.算法搜索至解空间树的任一节点时,总是先判断该节点是否肯定不包含问题的解.如果肯定不包含,则跳过对以该节点为根的子树的系统搜索,逐层向其原先节点回溯.否则,进入该子树,继续按深度优先的策略进行搜索. 运用回溯法解题通常包含以下三个步骤: · 针对所给问题,定义问题的解空间: · 确定易于搜索的解空间结构: · 以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函

回溯法之八皇后问题简单理解

回溯法,简单理解就是有源可溯.基本思想要借鉴穷举法,但是它不是一味地穷举,当发现某一步不符合条件时,这一步后面的穷举操作就不进行了(俗称“剪枝”),我自己把它叫做动态穷举法.假设第一个步骤可行,那么执行第二个步骤,第三个......如果其中第三个步骤不行,那么我们再回过来(回溯),第二个步骤换一种方法尝试,然后再重新第三个步骤,第四个......直到完成任务要求为止. 这里,以八皇后问题为例.试图把回溯法讲清楚. 注意:递归应该是一种算法结构,回溯法是一种算法思想. 何为八皇后问题? (百度百科

leetcode_401_Binary Watch_回溯法_java实现

题目: A binary watch has 4 LEDs on the top which represent the hours (0-11), and the 6 LEDs on the bottom represent the minutes (0-59). Each LED represents a zero or one, with the least significant bit on the right. For example, the above binary watch

回溯法、数独与N阶可达问题(一)

回溯法是剪了枝的穷举,这是字面上的说法,不太好理解,不如讲解实例来的酸爽,于是引出了N阶可达问题: 有N个国家,每个国家有若干城市,小明要从中国(任意一个城市)出发,遍历所有国家(假设这个遍历顺序已经定了),最终到达美利坚(任意一个城市).而城市之间有可能不可达,只有小明尝试过才知道(就是后面的check()函数),求满足要求的一条路径? 从上面的表述中我们已经嗅到了浓浓的穷举屌丝气质——遍历所有组合,但是我们的回溯思想总是基于这样一个简单的事实:如果当前选择导致你走进了死胡同,那么这个选择一定

拉丁矩阵问题 利用回溯法的C++实现方案

这两天正好在赶算法设计的作业,这里把做的几个需要写代码的题放上来,方便以后查看. 1.题目要求 2.算法思想 这个题目基本思想是 利用回溯法,对于 m 行 n 列, 本质上就是一个二维数组, 我们可以将问题的解写成 x[1],x[2],x[3] - x[m*n], 那么对于每个点 x[i] 的取值实际上是 [1, n], 套用回溯法的算法框架,这里的 约束条件 ,就是同行,同列 没有相同的取值, 并且这里没有优化目标,只要类似 N后问题找出所有解就可以了. 另外,回溯时候的边界条件,需要特别注意

回溯法:找回遗失的创造力

今天非常高兴可以听高老师的讲座,因为我跟高老师有渊源(当然是我认识他,他不认识我).我毕业后的第一份工作中有一小段时间在做Android手机应用的开发,那时Android正盛, 网上资料铺天盖地,但良莠不齐,想我在初出茅庐软件思想还不健全的情况下要找到一份通俗易懂并且靠谱的参考资料实在很困难.但非常幸运的是,我发现了高涣堂老师写的几本书,虽然内容不是很多但讲得非常清晰.那时正在自学设计模式,自认"满腹经纶却无用武之地",而他的书恰好弥补了我在实践方面的不足.我是个吃了鸡蛋,就想看看下蛋