第五组回溯算法(N皇后排列方法问题)
N皇后问题 回溯法
A2 A1 A B3 A2 A1 B2 C2 B1 C1
A
跳马问题
4. 从C1出发,可以有三条路径,选择D1。 但到了D1以后,我们无路可走且D1也不 是最终目标点,因此,选择D1是错误的, D3 B3 我们退回C1重新选择D2。同样D2也是错 B2 C2 误的。再回到C1选择D3。D3只可以到E1, A2 但E1也是错误的。返回D3后,没有其他 C1 A1 B1 选择,说明D3也是错误的,再回到C1。 A 此时C1不再有其他选择,故C1也是错误 的,退回B1,选择C2进行尝试。
x
path:array[1..m] of integer; 其中,path[i]:表示第i个节点所走的方向
方向t,下一步的位置就是 (x+dx[t], y+dy[t])。
跳马问题
约束条件:
不越界: (x + dx[i] <= n) and (y + dy[i] > 0) and (y + dy[i] <= n)
回溯
Backtracking
回溯 N皇后问题 跳马问题 迷宫问题 图的着色问题 0-1背包问题 装载问题 批处理作业调度 填数问题 组合输出问题 算24点问题 ACM应用
学习要点
掌握回溯的概念 掌握经典问题的回溯解决方法 掌握回溯与其它方法的异同
回溯法
有许多问题,当需要找出它的解集或者要求回答什么 解是满足某些约束条件的最佳解时,往往要使用回溯 法。 ► 回溯法的基本做法是搜索,或是一种组织得井井有条 的,能避免不必要搜索的穷举式搜索法。这种方法适 用于解一些组合数相当大的问题。 ► 回溯法在问题的解空间树中,按深度优先策略,从根 结点出发搜索解空间树。算法搜索至解空间树的任意 一点时,先判断该结点是否包含问题的解。如果肯定 不包含,则跳过对该结点为根的子树的搜索,逐层向 其祖先结点回溯;否则,进入该子树,继续按深度优 先策略搜索。
n皇后问题递归算法
n皇后问题递归算法想象一下,有一个棋盘,上面要放下几个皇后。
这个皇后可不是普通的角色,她可厉害了,能横着、竖着、斜着攻击其他棋子。
没错,皇后就是这么霸道,咱们要确保她们互不干扰。
这个事情听起来有点棘手,但其实只要动动脑筋,找到一些聪明的方式,问题就迎刃而解了。
说到这里,很多小伙伴可能会想:“这不是简单的下棋吗?有什么难的?”可是,等你真正上手的时候,嘿嘿,才发现事情并没有想象中那么简单。
我们从一个小小的例子开始吧。
假如棋盘是个4×4的小方块,咱们的目标就是在这个方块上放四个皇后。
听起来简单吧?但试想一下,放下第一个皇后没问题,她随便摆哪儿都行,棋盘上空荡荡的。
但是当你放下第二个皇后时,哎哟,她可就要考虑第一位皇后的地盘了,不能让她们互相看对方的脸呀。
接下来再放第三个、第四个,感觉压力山大,像是要参加一场马拉松比赛,刚开始风驰电掣,后来就感觉体力不支,越往后越是踌躇满志又心慌慌。
于是,咱们决定采用递归的方式来解决这个问题。
你说,递归是什么鬼?其实就是一种方法,简单点说就是“自己叫自己”。
我们可以从放第一个皇后开始,然后把剩下的事情交给下一个步骤,就像是把作业一层一层递交给老师,直到所有问题都解决为止。
我们可以把当前棋盘的每一行都视为一个新的阶段,逐行逐列地放置皇后。
如果这一步走不通,那就“咱们重来”。
这就像是人生中的很多事情,不顺利的时候就换个思路,继续前进。
当你把第一行的皇后放好,接下来就要检查第二行的每一个位置,看看哪里可以放。
每检查一个位置,就像是在打探敌情,谨慎又小心。
噢,不行,这个位置被第一行的皇后盯上了,得换个地方。
这样一来,你可能会发现,有的地方放不下,有的地方则一片大好,任你选择。
一直检查到棋盘的最后一行,如果成功放下皇后,哇,心里那种成就感,简直像中了大奖一样!可是如果发现放不下,那就要退回去,换个方案,哪怕是从头再来。
这就是递归的魅力,既简单又复杂,似乎在和我们的人生进行一场心灵的对话。
回溯法求解N皇后问题
① 如果xi+1= ai+1k不是集合Si+1的最后一个元素,则令xi+1= ai+ 1k+1,即选择Si+1的下一个元素作为解向量X的第i+1个分量;
② 如果xi+1= ai+1k是集合Si+1的最后一个元素,就回溯到X=(x1, x2, …, xi),选择Si的下一个元素作为解向量X的第i个分量,假 设xi= aik,如果aik不是集合Si的最后一个元素,则令xi= aik+1; 否则,就继续回溯到X=(x1, x2, …, xi-1);
global X(1:k); integer i,k;
i1
while i<k do
if X(i)=X(k) or ABS(X(i)-X(k))=ABS(i-k) then
return (false)
end if
ii+1 repeat return (true)
判断是否有其它的皇 后与之在同一列或同 一斜对角线上
HHIT
Algorithm
(1)如果X=(x1, x2, …, xi+1)是问题的最终解,则输出这个解。 如果问题只希望得到一个解,则结束搜索,否则继续搜索其
他解;
(2)如果X=(x1, x2, …, xi+1)是问题的部分解,则继续构造解 向量的下一个分量;
(3)如果X=(x1, x2, …, xi+1)既不是问题的部分解也不是问题 的最终解,则存在下面两种情况:
while k>0 do // 对所有的行,执行以下语句 //
X(k)X(k)+1 //移到下一列//
while X(k)<=n and Not PLACE(k) do //此处能放这个皇后吗//
X(k)X(k)+1 //不能放则转到下一列//
N皇后问题及答案解
N皇后问题及答案解题⽬在⼀张N∗N的国际象棋棋盘上,放置N个皇后,使得所有皇后都⽆法互相直接攻击得到,(皇后可以直接攻击到她所在的横⾏,竖列,斜⽅向上的棋⼦),现在输⼊⼀个整数N,表⽰在N∗N的棋盘上放N个皇后,请输出共有多少种使得所有皇后都⽆法互相直接攻击得到的⽅案数。
例如下⾯这样的摆法,是4皇后的⼀个解 (1代表有皇后,0代表没有)0 1 0 00 0 0 11 0 0 00 0 1 0输⼊⼀个整数N,代表皇后的个数输出输出⽅案数样例输⼊样例输⼊14样例输⼊28样例输出样例输出12样例输出292⼀、DFS+回溯(1)设已经放好的皇后坐标为(i,j),待放⼊的皇后坐标为(r,c),则它们满⾜以下关系:(1)不同⾏,即 i ≠ r;(2)不同列,即 j ≠ c;(3)不在斜对⾓线上,即 |i-r| ≠ |j-c|.可以在⼀⾏逐列尝试,这样就不⽤考虑(1)了。
#include <iostream>#include <algorithm>#include <cstring>using namespace std;int n, tot = 0;int col[15] = {0}, ans[15] = {0}; //col[i]的值为第i⾏的皇后的列数的值,即j,ans[]数组⽤来存放结果bool check(int c, int r) //检查是否和已经放好的皇后冲突{for (int i = 0; i < r; i++)if (col[i] == c || (abs(col[i] - c) == abs(i - r))) //因为是逐⾏放置,所以只考虑纵向和斜向return false;return true;}void dfs(int r,int m) //在第r⾏放皇后,m表⽰⾏数{if(r==m){ //r==m,即皇后放到最后⼀⾏,满⾜条件,tot++,返回;tot++;return;}for(int c=0;c<m;c++) //在此⾏逐列尝试if(check(c,r)){ //检查是否冲突col[r]=c; //不冲突就在此列放皇后dfs(r+1,m); //转到下⼀⾏继续尝试}}int main(){cin>>n;for (int i = 0; i <= 13; i++) //算出所有N皇后的答案,先打表,不然会超时{memset(col, 0, sizeof(col)); //清空col,准备计算下⼀个N皇后问题tot = 0;dfs(0,i);ans[i] = tot;}cout << ans[n] << endl;return 0;}在上述程序中,dfs()⼀⾏⾏放置皇后,时间复杂度为O(N!);check()判断冲突,时间复杂度为O(N),总的为O(N*N!)!⾮常的⾼。
N皇后问题求解
剪枝函数:
public static boolean check(int n) { for (int i = 1; i < n; i++) { if (a[i] == a[n] || (Math.abs(i - n) == Math.abs(a[i] - a[n]))) return false; } return true; } //分枝限界函数,其中,a[i]=a[n]表示皇后所// 在的列相等;//Math.abs(i - n) == //Math.abs(a[i] - a[n]))判断两皇后是否在同一 //对角线上。
2
(3,1) (3,2) (3,3) (3,4)) (4,4)
4
用n元组x[1:n]表示n皇 后问题的解,x[i]表示皇 后i放在第i 行的第x[i]列 上,用完全n叉树表示 解空间。 剪枝函数设计:对于两个皇后A(i,j)、B(k,l) 两个皇后不同行:i不等于k; 两个皇后不同列:j不等于l; 两个皇后不同一条斜线|i-k|≠|j-l|.
3,代码及运行结果
一、代码见“N皇后问题求解代码.doc”文档;
二、运行结果见“N皇后问题求解代码运行结 果.mp4”。
Thanks for your attention !!!
N皇后问题,是一个古老而著名
的问题,是回溯算法的典型例题: 在N*N格的格子上摆放N个皇后, 使其不能互相攻击,即任意两个 皇后都不能处于同一行、同一列 或同一斜线上,问有多少种摆法?
N=4时,可能的树结构:
通过限界条件我们可以得到如 下的搜索树:
当N=4时,即4皇后问题;下 图为4皇后的一种解:
在第1的m行为合理配置的基础上再配置第m1行直至第n行也是合理时就找到了一在每行上顺次从第一列到第n列配置当第n列也找不到一个合理的配置时就要回溯去改变前一列的配置
回溯法求N皇后问题
Tree-回溯法求N皇后问题#include <stdio.h>#include <malloc.h>#define N 4 //N皇后typedef int Chessboard[N + 1][N + 1]; //第0号位置不用bool check(Chessboard cb, int i, int j) { //看棋盘cb是否满足合法布局int h, k;int m = i + j, n = i - j;for(h=1; h<i; h++) {if(cb[h][j] == 1 && h != i) return false; //检查第j列if(m-h<=N && cb[h][m-h] == 1 && h != i) return false; //检查斜的,m-h<=N是为了保证不越界if(h-n<=N && cb[h][h-n] == 1 && h != i) return false; //检查斜的,h-n<=N是为了保证不越界}for(k=1; k<N; k++)/*检查第i行的*/ if(cb[i][k] == 1 && k != j) return false;return true;}void printfChessboard(Chessboard cb) {//打印棋盘int i, j;for(i=1; i<=N; i++) {for(j=1; j<=N; j++) printf("%d ", cb[i][j]);printf("\n");}printf("\n");}/*进入本函数时,在n*n棋盘前n-1行已放置了互不攻击的i-1个棋子。
现从第i行起继续为后续棋子选择合适位置。
回溯算法解决N皇后问题实验及其代码
实验报告4回溯算法实验4回溯算法解决N皇后问题一、实验目的1)掌握回溯算法的实现原理,生成树的建立以及限界函数的实现;2)利用回溯算法解决N皇后问题;二、实验内容回溯算法解决N皇后问题。
三、算法设计1)编写限界函数bool PLACE(int k,int x[]),用以确定在k列上能否放置皇后;2)编写void NQUEENS(int n)函数用以摆放N个皇后;3)编写主函数,控制输入的皇后数目;4)改进和检验程序。
四、程序代码//回溯算法解决N皇后问题的c++程序#include<math.h>#include<iostream>using namespace std;int count=0; //皇后摆放的可能性bool PLACE(int k,int x[]);//限界函数void NQUEENS(int n);//摆放皇后int main(){}int queen;cout<<"先生(女士)请您输入皇后的总数,谢谢!:"<<endl;cin>>queen;NQUEENS(queen);cout<<"所有可能均摆放完毕,谢谢操作"<<endl;return 0;void NQUEENS(int n){/*此过程使用回溯算法求出在一个n*n棋盘上放置n个皇后,使其即不同行,也不同列,也不在同一斜角线上*/int k, *x=new int[n];//存放皇后所在的行与列x[0]=0;k=0;while (k>=0&&k<n){ //对所有的行执行以下语句x[k]=x[k]+1; //移到下一列while(x[k]<=n&&(!PLACE(k,x))){ //此处能放置一个皇后吗?}if( x[k]<=n ) { //找到一个位置if( k==n-1 ){ //是一个完整的解吗cout<<"第"<<++count<<"排法是:"<<endl;for(int i=0;i<n;i++)//打印皇后的排列{}cout<<"\n";for (int j=0;j<n;j++){}cout<<"\n";if (x[i] == j+1){}else{}cout<<". ";cout<<"*";x[k]=x[k]+1; //移到下一列}}}}else { k=k+1; x[k]=0;} //移向下一行else k=k-1; //回溯bool PLACE(int k,int x[]){/*如果一个皇后能放在第k行和x(k)列,返回ture;否则返回false。
实验报告:回溯法求解N皇后问题(Java实现)
实验报告一、实验名称:回溯法求解N皇后问题(Java实现)二、学习知识:回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并以此慢慢地扩大问题规模,迭代地逼近最终问题的解。
这种迭代类似于穷举并且是试探性的,因为当目前的可能答案被测试出不可能可以获得最终解时,则撤销当前的这一步求解过程,回溯到上一步寻找其他求解路径。
为了能够撤销当前的求解过程,必须保存上一步以来的求解路径,这一点相当重要。
三、问题描述N皇后问题:在一个 N * N 的国际象棋棋盘中,怎样放置 N 个皇后才能使N 个皇后之间不会互相有威胁而共同存在于棋局中,即在 N * N 个格子的棋盘中没有任何两个皇后是在同一行、同一列、同一斜线上。
深度优先遍历的典型案例。
四、求解思路1、求解思路:最容易想到的方法就是有序地从第1列的第 1 行开始,尝试放上一个皇后,然后再尝试第2 列的第几行能够放上一个皇后,如果第 2 列也放置成功,那么就继续放置第 3 列,如果此时第3列没有一行可以放置一个皇后,说明目前为止的尝试是无效的(即不可能得到最终解),那么此时就应该回溯到上一步(即第 2 步),将上一步(第 2 步)所放置的皇后的位置再重新取走放在另一个符合要求的地方…如此尝试性地遍历加上回溯,就可以慢慢地逼近最终解了。
2、需要解决的问题:如何表示一个N * N 方格棋盘能够更有效?怎样测试当前所走的试探路径是否符合要求?这两个问题都需要考虑到使用怎样的数据结构,使用恰当的数据结构有利于简化编程求解问题的难度。
3、我们使用以下的数据结构:int column[col] = row 表示第 col 列的第 row 行放置一个皇后boolean rowExi sts[i] = true 表示第 i 行有皇后boolean a[i] = true 表示右高左低的第 i 条斜线有皇后(按→↓顺序从1~ 2*N -1 依次编号)boolean b[i] = true 表示左高右低的第 i 条斜线有皇后(按→↑顺序从1~ 2*N -1 依次编号)五、算法实现对应这个数据结构的算法实现如下:1.**2. * 回溯法求解N 皇后问题3. * @author haollo yin4. */5.public classN_Quee ns {6.7.// 皇后的个数8. privat e int queens Num = 4;9.10.// column[i] = j表示第 i 列的第 j 行放置一个皇后11. privat e int[] queens = new int[queens Num + 1];12.13.// rowExi sts[i] = true 表示第 i 行有皇后14. privat e boolea n[] rowExi sts = new boolea n[queensNum + 1];15.16.// a[i] = true 表示右高左低的第 i 条斜线有皇后17. privat e boolea n[] a = new boolea n[queens Num * 2];18.19.// b[i] = true 表示左高右低的第 i 条斜线有皇后20. privat e boolea n[] b = new boolea n[queens Num * 2];21.22.// 初始化变量23. privat e void init() {24. for (int i = 0; i < queens Num + 1; i++) {25. rowExi sts[i] = false;26. }27.28. for(int i = 0; i < queens Num * 2; i++) {29. a[i] = b[i] = false;30. }31. }32.33.// 判断该位置是否已经存在一个皇后,存在则返回true34. privat e boolea n isExis ts(int row, int col) {35. return (rowExi sts[row] || a[row + col - 1]|| b[queens Num + col - row]);36. }37.38.// 主方法:测试放置皇后39. public void testin g(int column) {40.41.// 遍历每一行42. for (int row = 1; row < queens Num + 1; row++) {43.// 如果第 row 行第 column列可以放置皇后44. if (!isExis ts(row, column)) {45.// 设置第 row 行第 column列有皇后46. queens[column] = row;47.// 设置以第 row 行第 column列为交叉点的斜线不可放置皇后48. rowExi sts[row] = a[row + column - 1] = b[queens Num + column - row] = true;49.50.// 全部尝试过,打印51. if(column == queens Num) {52. for(int col = 1; col <= queens Num; col++) {53. System.out.print("("+col +"," + queens[col] + ") ");54. }55. System.out.printl n();56. }else {57.// 放置下一列的皇后58. testin g(column + 1);59. }60.// 撤销上一步所放置的皇后,即回溯61. rowExi sts[row] = a[row + column - 1] = b[queens Num + column - row] = false;62. }63. }64. }65.66.//测试67. public static void main(String[] args) {68. N_Quee ns queen= new N_Quee ns();69. queen.init();70.// 从第 1 列开始求解71. queen.testin g(1);72. }73.}六、运行结果当N = 8 时,求解结果如下(注:横坐标为列数,纵坐标为行数):(1,1) (2,5) (3,8) (4,6) (5,3) (6,7) (7,2) (8,4)1.(1,1) (2,6) (3,8) (4,3) (5,7) (6,4) (7,2) (8,5)2.(1,1) (2,7) (3,4) (4,6) (5,8) (6,2) (7,5) (8,3)3.... ...4.... ...5.(1,8) (2,2) (3,4) (4,1) (5,7) (6,5) (7,3) (8,6)6.(1,8) (2,2) (3,5) (4,3) (5,1) (6,7) (7,4) (8,6)7.(1,8) (2,3) (3,1) (4,6) (5,2) (6,5) (7,7) (8,4)8.(1,8) (2,4) (3,1) (4,3) (5,6) (6,2) (7,7) (8,5)当N = 4 时,求解结果如下:1.(1,2) (2,4) (3,1) (4,3)2.(1,3) (2,1) (3,4) (4,2)七、实验小结:1、根据问题选择恰当的数据结构非常重要,就像上面 a 、b 标志数组来表示每一条斜线的编号顺序以及方向都相当重要。
算法设计与分析课件--回溯法-n皇后问题
8
5.5 n皇后问题
对于n皇后问题,搜索树有1+n+n2+…+nn个结点。 1+n+n2+…+nn= (nn+1 -1)/(n-1) <= (nn+1)/(n/2) = 2nn(n>=2) 在每个结点处,要判断该位置的皇后是否与已经放置的皇后相 互攻击,最多要看3n个位置(沿列的方向、主与副对角线方向)是 否已有皇后,故n皇后问题的该算法最坏时间复杂度为 O(3n*2nn)=O(nn+1),这是个粗略的估计。
1个皇后所在的列,即仅有n-i+1个位置可供选择。令 S={1,2,…,n},则xi∈S-{x1,x2,…,xi-1}。显然,满足显式约束的n 元组共有n!种,它构成n皇后问题的解空间。 排列树
10
5.5 n皇后问题
n皇后问题的分析: • 解空间树 – 排列树。这里n=4。
11
5.5 n皇后问题
9
5.5 n皇后问题
n皇后问题的分析(二):
✓ 问题解的形式:表示为n元组(x1, x2,…, xn)的形式,其中xi(i=1, 2,…, n)表示第i个皇后放置在第i行第xi列;
✓ 显式约束:n个皇后不同行且不同列; ✓ 隐式约束:n个皇后不在正反对角线上; ✓ 解空间:根据显式约束,第i(i=1,2,…,n)个皇后不能放置在前i-
if t > n then
OUTPUT(x);
else
for i 1 to n do
x[t] i;
//第t个皇后放到第i列,不去判断列是否冲突
if PLACE(t) then
BACKTRACK-NQUEEN1-REC(t+1);
回溯算法补充内容(N皇后、图的着色问题)
• 子集合数问题的递归回溯算法
• • Procedure Sumofsum(s,k,r) //找w(1:n)中和数为M的所有子集。进入此过程的时候x(1),…,x(k-1)的值已经确定。 s=w(1)x(1)+…+w(k-1)x(k-1); r=w(k)+…+w(n) 这些W(j)按非降次的顺序排列。假定 w(1)<=M, w(1)+…+w(n)>=M
N皇后问题的所有解
end while
end
PLACE (k)的程序
• Procedure PLACE(k) // 如果一个皇后能放在第k行和x(k)列,返回ture;否则返回false。x是一个全局 变量,进入此过程的时候已经置了k个值。ABS(r)过程返回r的绝对值 global x(1:k); integer i,k; i=1; while i<k do if x(i)==x(k) or ABS(x(i)-x(k))==ABS(i-k) //在同一列 或者在同一斜角线上 有两个皇后 then return (false) end if i=i+1; end while return (true) end
Global integer m,n X(1:n) Boolean Graph(1:n,1:n) integer k loop X(k)=(X(k)+1)mod(m+1) //试验下一个最高标值的颜色 if X(k)==0 return //全部颜色用完 endif for j=1:n if Graph(k,j)and X(k)==X(j) //如果(k,j)是一条边并且临近的结点有相同的颜色 exit endif endfor if j==n+1 // 找到了一种新颜色 return endif endloop end
n皇后问题非递归回溯算法
n皇后问题非递归回溯算法一、问题描述n皇后问题是一个经典的回溯算法问题,其目标是在一个n*n的棋盘上放置n个皇后,使得它们互相之间不能攻击。
即任意两个皇后都不能处于同一行、同一列或者同一斜线上。
二、问题分析1. 回溯算法思路回溯算法是一种通过穷举所有可能情况来找到所有解的算法。
在遍历过程中,如果发现当前状态不符合要求,则回溯到上一个状态进行下一步尝试。
2. 非递归实现传统的n皇后问题解法大多采用递归实现,但是递归实现会存在栈溢出等问题。
因此,我们可以采用非递归实现方式来避免这些问题。
三、算法设计1. 状态表示我们可以用一个数组board来表示当前棋盘状态,其中board[i]表示第i行皇后所在的列数。
2. 状态转移在每一行中,我们依次尝试将皇后放置在每一个位置上。
如果当前位置不符合要求,则继续尝试下一个位置;如果当前位置符合要求,则将该位置标记为已占用,并将当前状态入栈进入下一层搜索。
当搜索到第n层时,说明找到了一组解,将该解保存并回溯到上一层继续搜索。
3. 剪枝优化为了减少不必要的搜索,我们可以采用以下两种剪枝策略:(1)列冲突剪枝:如果当前位置所在列已经有皇后,则直接跳过该位置。
(2)斜线冲突剪枝:如果当前位置所在的左上、右上斜线已经有皇后,则直接跳过该位置。
四、代码实现1. 初始化首先,我们需要定义一个栈来保存状态,并将第一行的所有位置都尝试一遍。
同时,我们还需要定义一个二维数组visited来保存哪些列和哪些斜线已经被占用。
```pythondef solveNQueens(n: int) -> List[List[str]]:res = []stack = []visited = [[False] * n for _ in range(3)]for i in range(n):stack.append([i])visited[0][i] = Truevisited[1][i - 0 + n - 1] = Truevisited[2][i + 0] = True```2. 回溯搜索在搜索过程中,我们不断取出栈顶状态进行扩展。
用回溯法求解n皇后问题
c程序实现
分析问题
//求解的递归函数 void Queen(int i,int n) { if(i>n) Output(); else { for(int j=1;j<=n;++j) // j代表列值 { int k=1; x[i]=j;//重新换一个列值,这里就是体现回溯的地方 while(k<i) { if((x[k]-x[i])*(abs(x[k]-x[i])-abs(k-i))!=0)
分析问题
问题分析
如何保证任何两个皇后不再一 条斜线上?设两个皇后q1和q2放 在(i,j)和(k,l)位置上,如 果q1和q2在斜率为-1的对角线上, 那么i - j = k - l成立,如果在斜率 为1的对角线上,那么 i + j = k + l成立,由此可知只要 | i - k | ≠ | j - l |成立,q1和q2就不 再同一条斜线上。 |i-k|≠|j-l|
分析问题
2.确定解空间 用完全n叉树表示解空间,现在以n=4为例:
分析问题
问题分析
1
1
2 1 34
3 1 24
4 123
2 34
× ×× ×34 24 23 ×14 13 × 34 ×
√
24 14 12 23 13 12
√
4 3 4 2 3 2 4 3 4 1 3 1 4 2 41 21 3 2 31 2 1
回溯法的基本思想
回溯法的基本思想是在问题的解空间树上按 深度优先搜索策略,从根节点出发搜索整个解 空间。搜索过程中,每到达一个结点时,则判 断该结点为根的子树是否含有问题的解,如果 可以确定该子树中不含有问题的解,则放弃对 该子树的搜索,逐层向其祖先节点回溯。否则, 进入该子树。
采用回溯法编写n皇后问题的程序。
下面是使用回溯法编写n皇后问题的程序的示例(使用Python语言):def is_safe(board, row, col, n):# 检查当前位置是否安全# 检查列上是否有皇后for i in range(row):if board[i][col] == 1:return False# 检查左上方是否有皇后i = rowj = colwhile i >= 0 and j >= 0:if board[i][j] == 1:return Falsei -= 1j -= 1# 检查右上方是否有皇后i = rowj = colwhile i >= 0 and j < n:if board[i][j] == 1:return Falsei -= 1j += 1# 当前位置安全return Truedef solve_n_queens(board, row, n):# 如果所有行都放置了皇后,则找到一组解决方案if row == n:print_solution(board, n)return# 尝试在当前行的每个列上放置皇后for col in range(n):if is_safe(board, row, col, n):# 放置皇后board[row][col] = 1# 递归地解决下一行solve_n_queens(board, row + 1, n)# 回溯,移除皇后board[row][col] = 0def print_solution(board, n):# 打印解决方案for i in range(n):for j in range(n):print(board[i][j], end=" ")print()print()def n_queens(n):# 创建一个空白棋盘board = [[0 for _ in range(n)] for _ in range(n)]solve_n_queens(board, 0, n)# 测试n = 4 # 设置n的值,表示n皇后问题的规模n_queens(n)此程序使用回溯法解决n皇后问题,其中函数is_safe用于检查当前位置是否安全,函数solve_n_queens用于递归地解决下一行,并在找到解决方案时打印出来,函数print_solution 用于打印解决方案,函数n_queens是入口函数,用于设置问题的规模并调用solve_n_queens 函数开始解决问题。
回溯法之N皇后问题
回溯法之N皇后问题回溯法之N皇后问题1. 问题描述在n*n格的棋盘上放置彼此不受攻击的n个皇后。
按照国际象棋的规则,皇后可以攻击与之在同⼀⾏或同⼀列或同⼀斜线上的旗⼦。
n后问题等价于在n*n格的棋盘上放置n个皇后,任何2个皇后不放在同⼀⾏或同⼀列或同⼀斜线上。
2. 问题分析(以n=4皇后问题为例)有俩种解法,第⼀种采⽤解空间为N(4)叉树的解法、第⼆种是采⽤解空间为排列数的解法。
2.1. N(4)叉树的解法每个皇后在⼀⾏上有四个可选位置。
即每个⾮叶结点有4个⼦节点,4叉树如下:解向量:(x1,x2,x3,......,x n)显约束:任意俩皇后不同⾏。
隐约束:(1) 不同列:x i ≠ x j (2) 不处于同⼀正反对⾓线:|i - j| ≠ |x i - x j|核⼼代码:// 剪枝函数,排除同列和同⼀对⾓线的分⽀int place1(int k) {for (int j = 1; j < k; j++)if (abs(k - j) == abs(x[j] - x[k]) || x[j] == x[k])return 0;return 1;}// t > n代表当前解已经求出,将总数+1// 利⽤循环遍历节点的n叉,同时判断分叉是否符合条件// 符合条件的分叉继续遍历下去void BackTrack1(int t) {if (t > n)sum++;elsefor (int i = 1; i <= n; i++) {x[t] = i;if (place1(t))BackTrack1(t + 1);}}2.2 排列数的解法解向量:(x1,x2,x3,......,x n)显约束:任意俩皇后不同⾏、不同列。
x1,x2,x3,......,x n是1,2,3.......n排列隐约束:不处于同⼀正反对⾓线:|i - j| ≠ |x i - x j|核⼼代码:// 交换俩⾏皇后的位置// 实现切换排列数的分⽀作⽤void swap(int i, int j) {int tmp = x[i];x[i] = x[j];x[j] = tmp;}// 剪枝函数,排除在同⼀对⾓线上的情况int place2(int k) {for (int j = 1; j < k; j++)if (abs(k - j) == abs(x[j] - x[k]))return 0;return 1;}// t > n时表⽰当前排列符合条件,总数 + 1// 利⽤for循环,和swap函数,将节点对应的所有排列遍历⼀次// 同时采⽤剪枝函数,减去错误的分⽀// 对正确的分⽀继续求解下去// 最后递归求解结束后,再次调⽤swap函数将状态返回到原本的节点状态void BackTrack2(int t) {if (t > n) sum++;elsefor (int i = t; i <= n; i++) {swap(t, i);if (place2(t))BackTrack2(t + 1);swap(t ,i);}}3. 完整代码/*** 回溯法求解n皇后问题* 使⽤x解向量,x1,x2,x3分别表⽰在1,2,3⾏上皇后的列号**/#include <stdio.h>#include <stdlib.h>#define MAX 4/*** n 皇后个数* x 当前解* sum**/int n = MAX;int x[MAX + 1];long sum = 0;// 剪枝函数,排除同列和同⼀对⾓线的分⽀int place1(int k) {for (int j = 1; j < k; j++)if (abs(k - j) == abs(x[j] - x[k]) || x[j] == x[k])return 0;return 1;}// t > n代表当前解已经求出,将总数+1// 利⽤循环遍历节点的n叉,同时判断分叉是否符合条件// 符合条件的分叉继续遍历下去void BackTrack1(int t) {if (t > n)sum++;elsefor (int i = 1; i <= n; i++) {x[t] = i;if (place1(t))BackTrack1(t + 1);}}// 交换俩⾏皇后的位置// 实现切换排列数的分⽀作⽤void swap(int i, int j) {int tmp = x[i];x[i] = x[j];x[j] = tmp;}// 剪枝函数,排除在同⼀对⾓线上的情况int place2(int k) {for (int j = 1; j < k; j++)if (abs(k - j) == abs(x[j] - x[k]))return 0;return 1;}// t > n时表⽰当前排列符合条件,总数 + 1// 利⽤for循环,和swap函数,将节点对应的所有排列遍历⼀次// 同时采⽤剪枝函数,减去错误的分⽀// 对正确的分⽀继续求解下去// 最后递归求解结束后,再次调⽤swap函数将状态返回到原本的节点状态void BackTrack2(int t) {if (t > n) sum++;elsefor (int i = t; i <= n; i++) {swap(t, i);if (place2(t))BackTrack2(t + 1);swap(t ,i);}}void main() {for (int i = 0; i <= n; i++)x[i] = i;BackTrack1(1);printf("%d\n", sum);for (int i = 0; i <= n; i++)x[i] = i;sum = 0;BackTrack2(1);printf("%d\n", sum);system("pause");}。
用回溯法实现n皇后问题
今天学习了一个经典的算法题:N皇后问题。
在N*N格的棋盘上放置N个皇后,使这些皇后不能互相攻击,即皇后不能在同一行,同一列或者同一对角线上。
以下是看到的比较经典的算法。
解决该问题的最典型的算法就是回溯法。
在那些涉及到寻找一组解的问题或者求满足某些约束条件的最优解的问题中,有许多可以用回溯法来求解。
回溯法是一个既带有系统性又带有跳跃性的的搜索算法。
它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。
算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。
如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。
否则,进入该子树,继续按深度优先的策略进行搜索。
回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。
而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。
这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。
回溯法的基本思想:确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。
这个开始结点就成为一个活结点,同时也成为当前的扩展结点。
在当前的扩展结点处,搜索向纵深方向移至一个新结点。
这个新结点就成为一个新的活结点,并成为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。
换句话说,这个结点不再是一个活结点。
此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。
回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。
运用回溯法解题通常包含以下三个步骤:(1)针对所给问题,定义问题的解空间;(2)确定易于搜索的解空间结构;(3)以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索;回溯法可用递归实现:procedure try(i:integer);varbeginif i>n then 输出结果else for j:=下界 to 上界 dobeginx[i]:=h[j];if 可行{满足限界函数和约束条件} then begin 置值;try(i+1); end;end;end;回到N皇后问题的解决来,看看如何用回溯法解。
回溯法解决n皇后问题
n 皇 后 问 题N 皇后问题,是一个古老而着名的问题,是回溯算法的典型例题:在N*N 格的格子上摆放N 个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法? 1、定义问题的解空间首先以八皇后为例,可以用一棵树表示8皇后问题的解空间。
由于8皇后问题的解空间为8!种排列,因此我们将要构造的这棵树实际上是一棵排列树。
2、确定解空间树的结构给棋盘上的行和列从1到8编号,同时也给皇后从1到8编号。
由于每一个皇后应放在不同的行上,不失一般性,假设皇后i 放在第i 行上,因此8皇后问题可以表示成8元组(x 1, x 2, …, x 8), 其中xi (i =1, 2, …, 8)表示皇后i 所放置的列号。
这种表示法的显式约束条件是S i ={1, 2, 3, 4, 5, 6, 7, 8},i =1, 2, …, 8。
在这种情况下, 解空间为88个8元组组成,而隐式约束条件是没有两个xi 相同(即所有皇后必须在不同列上),且满足不存在两个皇后在同一条对角线上。
加上隐式约束条件,问题的解空间可进一步减小。
此时,解空间大小为8!,因为所有解都是8元组的一个置换。
图5-7表示了8皇后问题的一个解。
图5-7 8皇后问题的一个解为了简单起见,图5-8只给出了n =4时问题的一种可能树结构。
QQQQQQQQ8765432112345678图5-8 4皇后问题解空间的树结构在实际中,并不需要生成问题的整个状态空间。
通过使用限界函数来删除那些还没有生成其所有子结点的活结点。
如果用(x1,x2,…,x i)表示到当前E结点的路径,那么xi+1就是这样的一些结点,它使得(x1,x2,…,x i,x i+1)没有两个皇后处于相互攻击的棋盘格局。
在4皇后问题中,惟一开始结点为根结点1,路径为( )。
开始结点既是一个活结点,又是一个E结点,它按照深度优先的方式生成一个新结点2,此时路径为(1),这个新结点2变成一个活结点和新的E结点,原来的E结点1仍然是一个活结点。
N皇后问题—回溯算法经典例题
N皇后问题—回溯算法经典例题题⽬描述: N 皇后是回溯算法经典问题之⼀。
问题如下:请在⼀个 n×n 的正⽅形盘⾯上布置 n 名皇后,因为每⼀名皇后都可以⾃上下左右斜⽅向攻击,所以需保证每⼀⾏、每⼀列和每⼀条斜线上都只有⼀名皇后。
题⽬分析: 在 N 皇后问题中,回溯算法思路是每⼀次只布置⼀个皇后,如果盘⾯可⾏,就继续布置下⼀个皇后。
⼀旦盘⾯陷⼊死局,就返回⼀步,调整上⼀个皇后的位置。
重复以上步骤,如果解存在,我们⼀定能够找到它。
可以看到,我们在重复“前进—后退—前进—后退”这⼀过程。
问题是,我们不知道⼀共需要重复这个过程多少次,也不能提前知道 n 是多少,更不知道每⼀次后退时需要后退⼏⾏,因此我们不能利⽤ for 循环和 while 循环来实现这个算法。
因此我们需要利⽤递归来实现代码结构。
逻辑如下:当⽅法布置完当前⾏的皇后,就让⽅法调⽤⾃⼰去布置下⼀⾏的皇后。
当盘⾯变成绝境的时候,就从当前⽅法跳出来,返回到上⼀⾏,换掉上⼀⾏的皇后再继续。
我们定义 NQueens(n) ⽅法,它负责输出所有成⽴的 n×n 盘⾯。
其中 1 代表皇后,0 代表空格。
代码:def NQueens(n): #输出所有成⽴的n·n盘⾯cols = [0 for _ in range(n)] #每⼀⾏皇后的纵坐标res = [] #结果列表def checkBoard(rowIndex): #检查盘⾯是否成⽴,rowIndex是当前⾏数for i in range(rowIndex):if cols[i]==cols[rowIndex]: #检查竖线return Falseif abs(cols[i]-cols[rowIndex]) == rowIndex-i: #检查斜线return Falsereturn Truedef helper(rowIndex): #布置第rowIndex⾏到最后⼀⾏的皇后if rowIndex==n: #边界条件board = [[0 for _ in range(n)] for _ in range(n)]for i in range(n):board[i][cols[i]] = 1res.append(board) #把当前盘⾯加⼊结果列表return#返回for i in range(n): #依次尝试当前⾏的空格cols[rowIndex] = iif checkBoard(rowIndex): #检查当前盘⾯helper(rowIndex+1) #进⼊下⼀⾏helper(0) #从第1⾏开始return resprint(NQueens(4))代码分析: 在 NQueens() ⽅法中,我们会定义 helper(x) ⽅法帮助实现递归结构。
n皇后问题_回溯法_递归实现__解释说明
n皇后问题回溯法递归实现解释说明1. 引言1.1 概述本文主要讨论的是n皇后问题及其解决方法。
n皇后问题是一个经典的数学问题,旨在找到如何将n个皇后放置在一个nxn的棋盘上,使得所有皇后彼此之间不会互相攻击。
这个问题具有一定难度,但可以通过回溯法和递归实现来有效解决。
1.2 文章结构本文共分为五个部分:引言、n皇后问题、回溯法解决n皇后问题的步骤、递归实现n皇后问题解决方案的详细步骤与算法思路以及结论。
引言部分主要对文章内容进行概述和介绍,并给出本文的结构安排。
1.3 目的本文旨在通过对n皇后问题的深入研究和探讨,介绍回溯法和递归实现在解决该问题中的应用方法。
通过详细说明算法步骤和思路,帮助读者理解如何使用回溯法和递归实现有效地解决n皇后问题,并对两种方法进行评价与讨论。
同时,还展望了可能的未来研究方向,为读者提供更多思考和拓展的空间。
本文旨在为对n皇后问题感兴趣的读者提供有益的参考和指导。
(文章引言部分完)2. n皇后问题:2.1 问题描述:n皇后问题是一个经典的组合问题,其中n表示棋盘上的行数和列数。
在一个nxn的棋盘上,要放置n个皇后,并且要求任意两个皇后之间不得互相攻击(即不能处于同一行、同一列或同一对角线上)。
这是一个相当困难的问题,因为随着n的增大,可能的解法呈指数增长。
2.2 解决方法介绍:为了解决n皇后问题,可以使用回溯法和递归实现的组合算法。
回溯法是一种通过尝试所有可能情况来找到解决方案的方法。
它通过逐步构建解,并在遇到无效解时进行回溯。
而递归是把大规模的问题分解成相似但规模更小的子问题来求解。
2.3 回溯法和递归实现的关系:在解决n皇后问题中,回溯法是主要思想,而递归则用于辅助实现回溯过程。
在每一步尝试放置一个皇后时,会先判断该位置是否与之前已经放置好的皇后冲突。
如果没有冲突,则继续考虑下一个位置,并以递归的方式调用自身。
如果找到一个有效解时,会结束递归并返回结果。
如果所有位置都无法放置皇后,则回溯至上一步进行下一种尝试。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
实训一
N皇后排列方法问题的回溯算法与实现
一、设计目的
1)掌握N皇后排列方法问题的回溯算法;
2)进一步掌握回溯算法的基本思想和算法设计方法;
二、设计内容
1.任务描述
1)算法简介
回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。
但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再
走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
2)N皇后排列方法问题简介
在N*N格的棋盘上放置彼此不受攻击的N个皇后.按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子.N后问题等价于在N*N格的棋盘上放置N个皇后,任何2个皇后不放在同一行或同一列或同一斜线上.
3)设计任务简介
对于回溯类似的问题。
首先,要能理解该问题运用到的回溯的概念;其次,根据回溯相关的基本思想,找出相应的数学公式;最后,进行程序的设计和编写。
利用回溯的基本思想和计算步骤,有助于我们解决生活中遇到的各种数学问题。
4)问题分析
由于这是一个平面上棋子布局处理问题,因此,我们可以将问题看成是一个二维数组问题。
给八个皇后分别编号为1,2,…,8,其中第i个皇后放置在第i行上,并这就解决了不同皇后分别摆放在
不同列的问题,这样又可以把问题简化为一个一维数组的问题,假设用一维数组x[i]来存放皇后所放
置的列,对于第i个皇后,假设它存放在x[i]列上,则对应的x数组应满足如下的条件:[2]
1)因为一共只有8列,故x[i]的取值只能取1到8之间的数。
2)因为不同的皇后只能粗放在不同的列上,则对于任意的i和j,应满足如果i!=j,则x[i]!=x[j]
3)因为不同的皇后不能存放在同一对角线上,故连接两个皇后的直线的斜率应不能等于正负1,而
连接任意第i个皇后和第j个皇后(i与j不同)的直线的斜率的计算公式为:(x[i]-x[j])/(i-j),
即(x[i]-x[j])/(i-j)!=±1,即:|x[i]-x[j]|!=| i-j |
N皇后排列方法问题的表示方案
2.递推过程的抽象描述
本设计采用前向或后向递推公式。
用自然语言、伪程序设计语言或流程图等形式针对N皇后排列方法问题的求解(抽象地)描述递推过程……
3.解题思路
4.主要数据类型与变量
Backtrack (int t)//递归调用,回溯法
int nQueen(int n)//皇后排列的方法
int n;//皇后个数
5.算法或程序模块
class Queen
{
friend int nQueen(int);
private:
bool Place(int k);
void Backtrack(int t);
int n,
*x;
long sum;
};
bool Queen::Place(int k)
{
for (int j=1; j<k ;j++)
if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k])) return false;
return true;
}
void Queen::Backtrack (int t)
{
if (t>n)
sum++;
else
for (int i=1;i<=n;i++)
{
x[t]=i;
if (Place(t)) Backtrack(t+1);
}
}
int nQueen(int n)
{
Queen X;
X.n=n;
X.sum=0;
int*p=new int[n+1];
for(int i=0;i<=n;i++)
p[i]=0;
X.x=p;
X.Backtrack(1);
delete[]p;
return X.sum;
}
三、测试
1.方案
描述测试方案、测试模块、测试数据实例(文字数据、图或表等形式)……
2.结果
四、总结与讨论
通过构造函数,利用回溯思想编写代码,利用回溯的基本思想和计算步骤,有助于我们解决生活中遇到的各种数学问题。
附:程序模块的源代码
#include<stdio.h>
#include<math.h>
class Queen
{
friend int nQueen(int);
private:
bool Place(int k);
void Backtrack(int t);
int n,
*x;
long sum;
};
bool Queen::Place(int k)
{
for (int j=1; j<k ;j++)
if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k])) return false;
return true;
}
void Queen::Backtrack (int t)
{
if (t>n)
sum++;
else
for (int i=1;i<=n;i++)
{
x[t]=i;
if (Place(t)) Backtrack(t+1);
}
}
int nQueen(int n)
{
Queen X;
X.n=n;
X.sum=0;
int*p=new int[n+1];
for(int i=0;i<=n;i++)
p[i]=0;
X.x=p;
X.Backtrack(1);
delete[]p;
return X.sum;
}
void main()
{
int n;
printf("请输入皇后个数:\n"); scanf("%d",&n);
n=nQueen(n);
printf("有%d种方法\n",n);
}。