回溯法求解n皇后问题(复习)

Toretto·2022-12-12 16:09·1228 次阅读

回溯法求解n皇后问题(复习)

回溯法#

回溯法是最常用的解题方法,有“通用的解题法”之称。当要解决的问题有若干可行解时,则可以在包含问题所有解的空间树中,按深度优先的策略,从根节点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的搜索,继续查找该结点的兄弟结点,若它的兄弟结点都不包含问题的解,则返回其父结点——这个步骤称为回溯。否则进入一个可能包含解的子树,继续按深度优先的策略进行搜索。这种以深度优先的方式搜索问题的解的算法称为回溯法。它本质上是一种穷举法,但由于在搜索过程中不断略过某些显然不合适的子树,所以搜索的空间大大少于一般的穷举,故它适用于解一些组合数较大的问题。

总结一下:

一、基本定义

回溯法(back track method)是在包含问题的所有可能解的解空间树中,从根结点出发,按照深度优先的策略进行搜索,对于解空间树的某个结点,若满足约束条件,则进入该子树继续搜索,否则将以该结点为根结点的子树进行剪枝。

二、适用范围

可避免搜索所有的可能解,适用于求解组合数较大的问题。

三、n皇后问题#

问题:在n x n的棋盘上摆放n个皇后,而且n个皇后中的任意两个是不能处于同一行、同一列、或同一斜线上。

用数组x[i](1≤i≤n)表示n后问题的解。其中x[i]表示皇后i放在棋盘的第i行的第x[i]列。由于不允许将2个皇后放在同一列,所以解向量中的x[i]互不相同。2个皇后不能放在同一斜线上是问题的隐约束。对于一般的n后问题,这一隐约束条件可以化成显约束形式。设2个皇后放置位置为(i,j),(k,l):

显然,棋盘的每一行上可以而且必须摆放一个皇后,所以,n皇后问题的可能解用一个n元向量X=(x1, x2, …, xn)表示,其中,1≤i≤n并且1≤xi≤n,即第i个皇后放在第i行第xi列上

由于两个皇后不能位于同一列上,所以,解向量X必须满足约束条件:

xi≠xj (式1)

若两个皇后摆放的位置分别是(i, xi)和(j, xj),在棋盘上斜率为-1的斜线上,满足条件i-j= xi-xj,在棋盘上斜率为1的斜线上,满足条件i+j= xi+xj,综合两种情况,由于两个皇后不能位于同一斜线上,所以,解向量X必须满足约束条件:

|i-xi|≠|j-xj| (式2)

为了简化问题,下面讨论四皇后问题:

四皇后问题的解空间树是一个完全4叉树,树的根结点表示搜索的初始状态,对应Backtrack(1,x);从根结点到第2层结点对应皇后1在棋盘中第1行的可能摆放位置,从第2层结点到第3层结点对应皇后2在棋盘中第2行的可能摆放位置,依此类推。

完全4叉树,我只画了一部分,完整的应该是除了叶结点,每个内部结点都有四个子结点,k表示层数:

img

剪枝之后:

img

回溯法求解4皇后问题的搜索过程:

img

当然这个图只表示到找到的第一个解,我们知道还有另外一个解。

代码#

变量sum记录可行方案个数,初始为0;

n表示皇后个数,由用户输入;

x[]数组保存问题的解,表示皇后i放在棋盘的第i行第x[i]列,初始时各元素都为0,而我们目的是求出有多少组(x[1],x[2],x[3]……x[n])满足摆放条件;

output(int x[])函数作用是输出当前找到的一个可行解,只在搜索到叶节点时才会调用;

Place(int k,int x[])函数作用是,对当前行k以上的所有行(即1到k-1行)逐行进行检查,如果该行与上面任何一行相互攻击(即位于同一对角线上了或同列了:abs(i-k)abs(x[i]-x[k]) || x[i]x[k]),那么返回false,否则返回true;

Backtrack(int k,int x[])函数表示搜索解空间中第k层子树,k>n时,算法搜索至叶节点,得到一个新的n皇后互不攻击放置方案,那么输出该方案,可行方案数sum加1;k<=n时,当前扩展节点是解空间的内部节点,该节点有x[1],x[2],x[3]……x[n]共n个子节点,对每一个子节点,用函数Place检查其可行性,如果可行,以深度优先的方式递归地对可行子树搜索,如果不可行剪枝。

Copy
#include <iostream> #include <cmath> using namespace std; // 记录可行方案个数 int sum=0; // 表示皇后个数,由用户输入 int n; // 输出当前找到的一个可行解,只在搜索到叶节点时才会调用 int output(int x[]){ int i; for(i=1;i<=n;i++){ cout << "(" << i << "," << x[i] << ")" << " "; } cout << endl; return 0; } // 对当前行k以上的所有行(即1到k-1行)逐行进行检查 bool Place(int k,int x[]){ int i; for(i=1;i<k;i++){ if(abs(i-k)==abs(x[i]-x[k]) || x[i]==x[k]) return false; } return true; } // 见上文详解 int Backtrack(int k,int x[]){ int i; if(k>n){//如果是叶节点,直接输出找到的一个解 output(x); sum++; } else{//内部节点,如果满足约束条件,继续深度搜索 。i代表列数,从1到n for(i=1;i<=n;i++){ x[k]=i; if(Place(k,x)) Backtrack(k+1,x); } } } int main(){ int *x,i; cout << "输入皇后个数:" << endl; cin >> n; cout << endl; // 数组保存问题的解,表示皇后i放在棋盘的第i行第x[i]列 x=new int[n+1]; // 初始时各元素都为0 for(i=0;i<=n;i++){ x[i]=0; } Backtrack(1,x); cout << endl; cout << "解的个数:" << sum << endl; system("pause"); return 0; }

运行结果#

image

posted @   Galaxy不写bug  阅读(1228)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示
目录