使用回溯算法求解骑士游历问题
骑士游历问题(C语言代码)

骑⼠游历问题(C语⾔代码)关于骑⼠游历问题,⼤家可以想到的⽅法是回溯法和贪⼼算法。
回溯法的时间复杂度⽐较⾼,贪⼼算法的时间复杂度就好多了。
骑⼠游历问题问题描述:棋盘⼤⼩是8*8,骑⼠在棋盘任⼀⽅格开始游历。
要求骑⼠游历棋盘的每⼀个⽅格且每个⽅格只游历⼀次。
输出骑⼠的游历路径。
解决思路:dir0、dir1…dir7由⼩到⼤排列,每次选择具有最少出⼝的⽅向,假设为dir0。
如果骑⼠⾛到第n步时,没有出⼝可以选择,同时,骑⼠未完全遍历棋盘,那么骑⼠回溯到上⼀位置((n-1)步),选择dir1⽅向。
骑⼠游历依次进⾏下去。
直⾄⾛到终点或回到原点。
终点代表骑⼠游历完成,原点代表骑⼠游历失败(没有可以游历每个⽅格游历且只游历⼀次的路径)。
这⾥假设当前位置的⼋个⽅向已经按照“具有出⼝”由⼩到⼤排列。
编写程序过程要解决的问题(先考虑简单的问题):⼀、每个位置最多有8个⽅向可以移动分别为:(-2,1) (-1,2) (1,2) (2,1) (2,-1) (1,-2) (-1,-2) (-2,-1);主函数中骑⼠在初始位置时,对个变量进⾏初始化代码:step=1;chessboard[startx][starty]=step;step++;cur_x=startx;cur_y=starty;dir[startx][starty]=max_dir;last_dir=max_dir;考虑到解决骑⼠游历需要⽤到多个函数。
骑⼠前进、后退函数可能⽤到这⼋个⽅向移动的坐标。
故将⼋个⽅向移动的坐标定义为全局变量,“intktmove_x[max_dir],ktmove_y[max_dir];”。
⼆、判断骑⼠应该前进还是回溯:骑⼠在当前位置有可移动的⽅向则前进,反之,回溯到上⼀位置。
⼋⼋棋盘上⾯的每个⽅格最多有⼋个⽅向可以移动。
棋盘数组:“int chessboard[width][width];”,则可以定义⼀个三维数组来判断⽅格某⼀⽅向是否被访问过:“int is_visted[width][width][max_dir]={0};”,初始化为0,表⽰64个⽅格具有的出⼝⽅向(最多有⼋个)未被访问,两个数组同样是全局变量。
回溯算法————备课用

(1,1) (1,2) (1,3) (1,4) (1,5) (2,1) (2,2) (2,3) (2,4) (2,5) (3,1) (3,2) (3,3) (3,4) (3,5)
(4,1) (4,2) (4,3) (4,4) (4,5)
方法来快速的得到解,因此,我们只能采取 最基本的枚举法来求解。 但我们知道,在n×n的棋盘上放置n个棋子 n 的所有放置方案有Cn 2种,而这个数字是非常 庞大的,直接枚举肯定会超时。
分析: N=4时,右图是一组解
要素一: 解空间
一般想法:利用二维数组,用[i,j]确定一个皇后位置! 优化:利用约束条件,只需一维数组即 可! x:array[1..n] of integer; x[i]:i表示第i行皇后 x[i]表示第i行上皇后放第几列
4.1骑士游历问题分析
骑士并不是在任何位置的下一步都有8种选择,选择 的减少由以下原因造成:
当前位置靠近棋盘的边缘 某些选择已经是走过的格 子,不能再重复。因此, 走过的路需要标记,回退 的路也要清除标记
4.1 回溯法解决骑士游历问题
规定“下一个选择的顺序”—这在所有回溯问 题中都很重要,只是有的问题顺序太明显而不 需要定义 每一个格子用坐标表示,每个格子增加一个标 记变量,表示该格子走过与否
回溯部份: 即状态恢复,使其恢复到进 入该分支前的状态,继续新的 分支 x[k]:=0; Dec(k);
程序实现: 回溯算法可用非递归和递归两种方法实现!
非递归写法: begin x[1] ← 0 Flag ← true k←1 while k>0 do begin While flag do x[k] ← x[k] +1 while x[k]<=n and (not place(k)) do begin K:=k+1; x[k] ← x[k] +1; if x[k]<=n then if k=n then sum ← sum+1 else begin k ← k+1 x[k] ← 0 end else k ← k-1 end
骑士巡游

骑士巡游问题
算法描述
初始化空棋盘(起始状态);
开始准备放子,直至出现回溯 在当前位置周围的8个位置中查找下一个可以放置骑士的 位置 如果找到了可以摆放的位置 放下一个子 如果已经是最后一次,即n*n 得到一个解 撤掉该子,继续寻找下一个解 否则(未到最后一次) 准备处理下一个位置 否则(没有找到可以摆放的位置) 回溯到上一行,并撤掉该位子的棋子
骑ቤተ መጻሕፍቲ ባይዱ巡游
算法如下: bool solve(int k,int i,int j) { 设置数组; 查找所有8种可能的移动 static int dx[]={0,2,1,-1,-2,-2,-1,1,2},dy[]={0,1,2,2,1,-1,-2,-2,-1}; //<假设方格移动到k> if(k==n*n) //成功,返回true result=true; else { //假设结果为false result=false; m=0; //查找所有8种可能的移动,并查看是否有某种移动可以得到解决方案 while(判断是否可以巡游) { u=i+dx[m],v=j+dy[m]; if(可以巡游) board[u][v]=k; sovle(k+1,u,v); m++; } if(board[i][j]==0) board[i][j]=0; result=false; } return result; }
应用步骤
1、确定问题状态结构; 2、分析问题状态空间树; 3、确定深度搜索与回溯规则; 4、确定解状态判别规则;
骑士巡游问题
回溯过程分析
1、从空棋盘起,选择第一个棋子。 2、在一个布局中放下一个棋子,即推演 到一个新的布局。 3、如果当前位置上没有可合法放置棋子 的位置,则回溯到上一行,重新布放上一 位置的棋子。
跳马问题骑士遍历问题

如下图所示,一只马在棋盘的某一点,它可以朝8 个方向前进,方向向量分别是: (-2,1)、 (-1,2) (1,2)、 (2,1)、(2,-1) 、(1,-2)、 (-1,-2)、(-2,-1)。
从中任选择一个方向前进,到达新的位置。 在从新的位置选择一个方向前进,继续,直 到无法前进为止。无法前进可能有如下原因: 下一位置超出边界、下一位置已经被访问过。 当马已经无法前进时,就回退到上一位置, 从新选择一个新的方向前进;如果还是无法 前进,就再回退到上一位置,以此类推。
❖ cout<<i1<<"列"<<'\t';
❖ for(int i=1;i<=n;i++)
❖{
❖ cout<<endl;
❖ cout<<i<<"行"<<'\t';
❖ for(int j=1;j<=n;j++)
❖{
❖ cout<<qipan[i][j]<<'\t';
❖}
❖ cout<<endl;
❖}
❖}
经分析,本问题可以运用回溯法的思想求解:
1.该问题的解空间的组织形式是一颗八叉树, 一个可行的解就是从根节点到叶子节点的一 条路径。
2.控制策略则是马必须在棋盘内。
代码
❖ #include<iostream.h>
❖ #include <time.h>
❖ #include <stdlib.h>
❖ const int n=6; // 表示棋盘的长和高n
骑士的游历问题

骑士的游历问题一问题描述:设有图所示的一个棋盘,在棋盘上的A点,有一个中国象棋的马,并约定马走的规则:(1)马走日字(2)马只能向右走找出一条从A到B的路径二、算法分析:1、马从A点出发,根据上面两条马走的规则,只能到达A1,A2两点,但究竟哪条路径能到达点呢?无法预知,只能任意选择其中的一个点。
2、当马到达A1点后,下面又有三种可能的选择,B1,B2,B3,由于无法预知走哪条路,所以任选一点(B1),马到达B1之后,也有2种选择C1,C2,由于无法预确定哪条路径是正确的,故任选一个C1。
从C1出发,也有3 个点可供选择D1,D2,D3,下面选择D1,当马到达D1之后,没有到达目标,而且已无路可走,所以选择D1是错误的,因此从C1出发另选一条路D2,结果与D1相同,没有到达目标,同时也无路可走。
因此,从C1出发最后可选择D3。
D3虽然不是目标,但是还可以继续走到E1,同样在马到达E1后,没有到达目标,同样也无路可走,因此是错误的,同时可知从D3也发,E1是唯一的一条路径,因此到达D3之后就错误了,再回到了C1,即从C1出发所有的路径都不可能达目标,因此,到达C1就错了,再回溯到B1,已确定到C1是错误的,因此改走C2。
3、从C2出发,可能到达的下一个点为D4,D5,D3,由于无法确定哪条路是正确的,因此选择D4,最后到达E2,再到达B。
此时,我们找到一条路径A—>A1—>B1—>C2—>D4—>E2—>B4、上面过程就是回溯算法的简单过程,核心思想有下面二点:(1)无法知道走哪条路正确时,只能任选一条路试试看。
(2)当从某点出发,所有可能的路径都不到达目标时,说明到达此点是错误的,必须回到此点的上一个点,然后重新选择另一个点来进行。
三、程序设计1、棋盘用坐标表示,A(0,0),B(8,4),其中点P(x,y)表示棋盘上任一个点,p用一对坐标(x,y)表示,而x,y的范围为:0<=x<=8, 0<=y<=42、根据马只能走的规则,x的数值永远是增加的。
第十一章 动态程序设计方法

第十一章 动态程序设计方法在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段,在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。
当然,各个阶段决策的选取不是任何确定的,它依赖于当前面临的状态,又影响以后的发展,当各个阶段决策确定后,就组成一个决策序列,因而也就确定了整个过程的一条活动路线(图11.1.1)。
这种把一个问题看作是一个前后关联具有链状结构的多阶段过程就称为多阶段决策过程,这种问题就称为多阶段决策问题。
图11.1.1在多阶段决策问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有“动态”的含义,我们称这种解决多阶段决策最优化的过程为动态程序设计方法。
应当指出,动态程序设计方法是考察求解多阶段决策问题的一种途径、一种方法,而不是一种特殊算法。
不象前面所述的那些解析法或搜索法那样,具有一个标准的数学表达式和明确定义的一组规划。
因此读者在学习时,除了要对基本概念和方法正确理解外,必须具体问题具体分析处理,以丰富的想象力去建立模型,用创造性的技巧去求解。
§11.1 基本概念我们先引入动态程序设计的一些概念、术语和符号: 1. 阶段k一个实际问题可能要有多次决策。
为此对整个问题,可按其特点划分成需要作出选择的若干轮次。
这些轮次即称为阶段。
阶段是前后关联的。
我们从第1个阶段开始,按照阶段的先后顺序进行决策。
2. 状态u k某一阶段的出发位置称为状态,通常一个阶段包含若干状态,因此u k 是一个状态集。
我们将始点至k 阶段的u k ’(u k ’∈u k )状态所获得的最小费用设为I[u k ’]。
设初始状态为u 0’。
显然初始时,对所有状态u 来说 ⎪⎩⎪⎨⎧≠∞==0'0][u u u u u I3. 决策x k在对问题的处理中作出的每种选择性的行动就是决策,即从该阶段的每一个状态出发,通过一次选择性的行动转移至下一阶段的相应状态。
编写程序求解骑士走棋盘问题的思路

福州大学数学与计算机科学学院科研实训专业:数学与应用数学年级:2008级学号:030801218姓名:唐昌宏指导教师:林华目录情景描述--------------------------------------------------------------------3 编写程序求解骑士走棋盘问题的思路--------------------------------3 设计心得总结--------------------------------------------------------------4 相关算法简介--------------------------------------------------------------4 源代码-----------------------------------------------------------------------5 时空分析--------------------------------------------------------------------6 心得体会--------------------------------------------------------------------7课题:骑士走棋盘算法探究一情景描述骑士旅游在十八世纪初倍受数学家与拼图迷得注意,骑士的走法为西洋棋的走法,骑士可以由任意位置出发,它要如何走完所有的位置?骑士走法简介:首先,国际象棋的棋盘如下骑士的走法为:先横或竖1或2格,再竖或横2或1格,没有中国象棋蹩脚的限制。
如:从图中的(a,1)格跳到(b,3)或(c,2)格二编写程序求解骑士走棋盘问题的思路编写程序求解骑士走棋盘问题的思路:在n行n列的棋盘上(如n=5),假设一位骑士(按象棋中“马走日”的行走法)从初始坐标位置(x1,y1)出发,要遍访(巡游)棋盘中的每一个位置一次。
骑士巡游问题[回溯法]
![骑士巡游问题[回溯法]](https://img.taocdn.com/s3/m/dfbb9526905f804d2b160b4e767f5acfa1c783d9.png)
骑⼠巡游问题[回溯法]问题: 在 n × n ⽅格的国际象棋棋盘上,马(也称为骑⼠Knight)从任意指定的⽅格出发,以跳马规则(横⼀步竖两步或横两步竖⼀步),周游棋盘的每⼀个格⼦,要求每个格⼦只能跳过⼀次。
思路: 搜索部分就是普通的回溯 void KnightPatrol(int n, int count, step steps[], int map[][8]){//n表⽰棋盘规模,count表⽰骑⼠已经⾛过的步数,steps记录骑⼠巡游过的点,map⽤来标记地图上每个点有没被⾛过if (count == n*n){//如果骑⼠巡游过了所有的点display(steps, n);}for (int i = N; i <= NW; i++){//对每个⽅向遍历step then = overlook(steps[count], i);//向i⽅向眺望⼀下bool lonely = false;if (count % 2 == 0){lonely = hasLonelyPoint(map, n);}if (isInside(n, then) && !isOverlap(map, then) && !lonely){//如果骑⼠下⼀步没有跑到地图外⾯,且没有⾛到刚才⾛过的地⽅//并且地图中没有⾛不到的点patrol(steps, count, then, map);KnightPatrol(n, count + 1, steps, map);map[steps[count + 1].x][steps[count + 1].y] = 0;}}}但是我这⾥设计了⼀个剪枝函数,具体思路就是每⾛⼀步,就判断下地图中是否有永远不能被⾛到的点就是因为这个棒棒哒剪枝函数,运⾏速度提⾼了不知道多少bool hasLonelyPoint(int map[][8], int n){//判断地图中是否有孤⽴的点,即怎么都⾛不到的点step point1, point2;for (int i = 1; i <= n; i++){for (int j = 1, count = 0; j <= n; j++){if (map[i][j] == 0){point1.x = i;point1.y = j;int count = 0;for (int k = N; k <= NW; k++){point2 = overlook(point1, k);if (!isInside(n, point2)) count++;//point1 周围的8个点中有点在地图外if (map[point2.x][point2.y] == 1) count++;//point1 周围的8个点中有点被踩过}if (count >= 8) return true;//如果8个⽅向全都不⾏,说明这个点永远都⽆法被⾛到了}}}return false;}。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
求解骑士游历问题显然求解骑士游历问题的每一步就是马在棋盘上走的一步。
在每一步马需要选择一个方向进行游历,这时记住解的每一步需要记住两件事:1.当前步的行列位置2.当前步已经试探过哪些方向了,以便回溯回来时能够选择一个新的方向进行试探所以使用两个数组,数组board记住棋盘的每个位置是在马的第几步到达的,这反映了问题的解,即第几步到哪个位置。
数组direction记住在棋盘的某个位置已经试探过的方向,每个位置有八个方向,可按某种顺序对八个方向编号,然后在每个位置按编号顺序试探方向。
在确定数据结构之后,同样需要确定下面几个问题:1.怎样的状态是初始状态。
2.怎样选择当前步可能的路线3.怎样表示向前推进一步4.怎样回溯及清除当前步的痕迹显然初始状态是棋盘的每个位置都置为第0步到达(即还没有到达),每个位置都还没有选择任何方向(可赋值MAX_DIR(=8)表示没有选择方向)。
选择当前步可能的路线就是在当前位置选择一个方向来游历下一步。
在选择的时候同样需要区分是从第0个方向开始考虑还是从上一次的下一个方向开始考虑。
为了方便从下一个方向开始考虑,实际上数组direction在某一位置(curr_x, curr_y)的值记住的是从上一位置选择了哪个编号的方向而到达的,这样容易回溯到上一位置,而且容易在回溯到上一位置之后从下个一方向重新试探。
向前推进一步则要根据所选择的方向推进到下一位置,记住到下一位置所选择的方向,下一位置是第几步到达的,然后增加步数。
回溯一步则要标记当前位置没有到达过(将到达的步数置为0),根据上一步到当前位置的所选择的方向(这个方向是记录当前位置所对应的direction数组中)而回溯到上一位置,然后减少步数。
下面程序用类KNIGHT来封装数组board、direction、当前位置(curr_x, curr_y)、当前步数(step),并且使用last_direction来记住上一位置到当前位置所选择的方向。
为了方便计算选择一个方向后从当前推进到下一位置,使用数组var_x和var_y来记住每个方向在x方向和y方向的改变值。
这个类中提供的方法的含义与类QUEEN类似。
为节省篇幅起见,我们将类的界面、实现及演示放在了同一文件。
// 文件:KNIGHT.CPP// 功能:使用回溯算法求解骑士游历问题#include <iostream.h>#include <iomanip.h>enum BOOLEAN {TRUE = 1,FALSE = 0};const int MAX_WIDTH = 30;const int MAX_DIR = 8;class KNIGHT {public:// FUNCTION: 设置初始状态KNIGHT(int width);// FUNCTION: 用比较直观的方式打印问题的解// REQUIRE: 必须先调用了成员函数tourist()void print();// FUCTION: 根据马的起始位置(start_x, start_y)使用回溯算法求骑士游历问题的一个解// REQUIRE: (start_x, start_y)必需在所设置的棋盘宽度范围内BOOLEAN tourist(int start_x, int start_y);protected:// FUNCTION: 初始化记录所选方向的数组,将每个值置为MAX_DIRvoid init_direction();// FUNCTION: 初始化记录马在第几步到位每个位置的数组,将每个值置为0 void init_chessboard();// FUNCTION: 设置初始状态,包括初始化方向数组和棋盘数组,并设置马的初始位置void set_start(int x, int y);// FUNCTION: 在当前位置选择一个方向以推进到下一位置// RETURN: 如果可选择一个方向推进则返回TRUE,否则返回FALSE// NOTE: 将该函数定义为虚函数,以便下面快速游历的类来重定义该函数而产生动态绑定virtual BOOLEAN select_direction();// FUNCTION: 从当前位置回溯到上一位置// NOTE: 将该函数定义为虚函数,以便下面快速游历的类来重定义该函数而产生动态绑定virtual void backward();// FUNCTION: 从当前位置推进到下一位置// NOTE: 将该函数定义为虚函数,以便下面快速游历的类来重定义该函数而产生动态绑定virtual void forward();// FUNCTION: 判断马是否能够走向位置(x, y)。
// RETURN: 如果马已经到过该位置,或该位置超出棋盘范围返回FALSE,否则返回TRUEBOOLEAN is_legal(int x, int y);// FUNCTION: 判断是否回溯到初始状态// RETURN: 如果步数回到第1步则表示回到初始状态而返回TRUE,否则返回FALSEBOOLEAN back_to_start();// FUNCTION: 判断是否游历完所有位置// RETURN: 如果步数等于棋盘格子数则表示游历完所有位置而返回TRUE,否则返回FALSEBOOLEAN is_end();// 下面两个数组用来记住选择某个方向后,推进到下一位置时x方向和y方向的值的变化int var_x[MAX_DIR];int var_y[MAX_DIR];// 记录马第几步到达某个位置的棋盘数组int chessboard[MAX_WIDTH][MAX_WIDTH];// 记录马在某个位置是在上一位置选择第几个方向到达的int direction[MAX_WIDTH][MAX_WIDTH];int width; // 棋盘宽度int curr_x, curr_y; // 马的当前位置int step; // 已经游历的步数int last_direction; // 上一位置到当前位置所选的方向};KNIGHT::KNIGHT(int width){this->width = width;init_direction();total_step = 0;}void KNIGHT::print(){int x, y;cout << " +";for (x = 0; x < width; x = x + 1) cout << "----+";cout << '\n';for (x = 0; x < width; x = x + 1) {cout << " |";for (y = 0; y < width; y = y + 1) cout << setw(3) << chessboard[x][y] << " |";cout << '\n';cout << " +";for (y = 0; y < width; y = y + 1) cout << "----+";cout << '\n';}}BOOLEAN KNIGHT::is_legal(int x, int y){if (x < 0 || x >= width) return FALSE;if (y < 0 || y >= width) return FALSE;if (chessboard[x][y] > 0) return FALSE;return TRUE;}BOOLEAN KNIGHT::back_to_start(){if (step == 1) return TRUE;else return FALSE;}BOOLEAN KNIGHT::is_end(){if (step > width * width) return TRUE;else return FALSE;}void KNIGHT::set_start(int x, int y){curr_x = x; curr_y = y; step = 1;chessboard[x][y] = step; step = step + 1;direction[x][y] = MAX_DIR;last_direction = MAX_DIR;}BOOLEAN KNIGHT::select_direction(){int try_x, try_y;// last_direction为MAX_DIR表示当前位置是一个新的位置,在新推进到某个位置(curr_x, curr_y)时,// direction[curr_x][curr_y]会记录上一位置到(curr_x, curr_y)时所选择的方向,这时// last_direction置为MAX_DIR用来标记该位置是新推进的位置。
if (last_direction == MAX_DIR) last_direction = 0;else last_direction = last_direction + 1;while (last_direction < MAX_DIR) {// 看下一步推进到哪个位置是合法的,如果合法则选择该方向。
try_x = curr_x + var_x[last_direction];try_y = curr_y + var_y[last_direction];if (is_legal(try_x, try_y)) break;last_direction = last_direction + 1;}if (last_direction == MAX_DIR) return FALSE;else return TRUE;}void KNIGHT::backward(){step = step - 1;chessboard[curr_x][curr_y] = 0;// 将last_direction置为上一位置到(curr_x, curr_y)所选择的方向last_direction = direction[curr_x][curr_y];// 根据这个方向回溯到上一位置,同时回溯到上一位置之后,在上一位置再试探时应该从// last_direction+1的方向开始。