八数码难题的搜索求解演示
- 格式:docx
- 大小:174.74 KB
- 文档页数:17
a算法八数码问题例题八数码问题是一个经典的搜索问题,其中有一个3×3的格子,其中包含了一些数字(通常是1到8),以及一个空白格。
目标是使用最少的步骤将格子中的数字排列成给定的目标顺序。
每个步骤可以是以下三种操作之一:1. 上下移动(将行中的某个数字上移或下移)2. 左右移动(将列中的某个数字左移或右移)3. 旋转(以中心为中心旋转整个格子)下面是一个使用A算法解决八数码问题的例子:假设初始状态如下:```markdown4 1 2 756 3 8 05 0 3 2 4 167 86 7 5 8 3 4 2 1 0```目标状态如下:```markdown1 2 3 4 5 6 7 8 00 3 6 7 4 5 8 1 27 8 5 6 1 2 3 4 0```下面是使用A算法解决这个问题的步骤:1. 首先,我们需要构建一个优先级队列(例如最小堆),用于存储所有可能的移动。
在这个例子中,每个移动都有一个成本和优先级。
成本是从当前状态到目标状态的最短路径长度,优先级是当前状态到目标状态的H启发式估计。
H启发式估计通常是当前状态和目标状态之间的曼哈顿距离。
2. 从队列中取出优先级最高(即成本最低)的移动。
在这个例子中,初始状态的移动是"上下移动"。
3. 应用这个移动,并更新所有相关状态的成本和优先级。
在这个例子中,我们将第一行向下移动一格。
然后,我们需要重新评估所有可能的状态,并更新优先级队列。
4. 在更新优先级队列后,我们需要检查是否已经达到目标状态。
如果已经达到目标状态,则算法结束。
否则,我们重复步骤2和步骤3,直到达到目标状态。
5. 在这个例子中,我们需要进行多次上下移动、左右移动和旋转,才能将数字排列成目标顺序。
每次应用移动后,都需要重新评估所有可能的状态,并更新优先级队列。
八数码问题解释8数码问题又称9宫问题,源于一个古老的智力游戏。
说白了就是我们小时候玩的“华容道”。
意在给定的9格棋盘的8个格子内分别放一个符号,符号之间互不相同,剩下一格做为“出口”。
我们把8个符号在棋盘上的排列顺序称作8数码的状态,游戏要求给定一个初始的状态与一个终止状态,符号要经过若干次移动后由初态变成终态,这个过程中只有“出口”附近的符号可以朝“出口”的方向移动,且每次只能移动一个符号。
如下图所示,(其中我们用0表示出口,=》表示移动一次,=》*表示移动0-n次):初态终态1 2 3 1 2 3 0 1 24 5 6 =》 4 5 6 =》* 3 4 57 8 0 7 0 8 6 7 82 解决方案通过观察我们可以发现每一次8数码的状态都可以通过移动字符变成有限的几种其他状态,比如上图中我们可以知道初态“出口”附近有8和6可以移动,那么这个初态可以经过移动得到两个新的状态。
我们人在玩这个游戏的时候,总是要做下面几个步骤:1.看看哪个符号可以移动。
2.判断一下哪个符号的移动最有利于到达终态。
3.选定一个符号并移动它。
4.判断是否到达终态,是则结束,否则就回到第一步。
而现在我们要使用机器来模拟这一过程,其步骤与人类类似,但不同的是,人在执行第二部的时候总是能预先判断未来好几步的局势,从而选出最有利的一步,而机器则不行,它要先得到一个状态才能知道这个状态下一步将会到哪些状态而无法像我们一样一次就看到后面几步的状态。
那么基本思想就是让机器穷尽由初态出发到达所有可能状态的路径,并从中找到有终态的路径作为问题的解。
2.1 A*算法就如我们上面说到的让机器找出所有的可能来得到问题的解,看起来似乎很简单,但问题在于一旦8数问题的解达到一定规模,机器所要穷尽的路径数量将变得极为庞大,无疑会消耗大量的时间和空间。
那么如何让机器像人一样在选择移动符号的时候总是能选择最有利的那一个呢?下面就要介绍启发式搜索中的一个算法A*算法来解决这个问题。
⼋数码难题(8puzzle)深度优先和深度优先算法1 搜索策略搜索策略是指在搜索过程中如何选择扩展节点的次序问题。
⼀般来说,搜索策略就是采⽤试探的⽅法。
它有两种类型:⼀类是回溯搜索,另⼀类是图搜索策略。
2 盲⽬的图搜索策略图搜索策略⼜可分为两种:⼀种称为盲⽬的图搜索策略,或称⽆信息图搜索策略;⽽另⼀种称为启发式搜索策略,⼜称为有信息的图搜索策略。
最常⽤的两种⽆信息图搜索策略是宽度优先搜索和深度优先搜索。
2.1 宽度优先搜索它是从根节点(起始节点)开始,按层进⾏搜索,也就是按层来扩展节点。
所谓按层扩展,就是前⼀层的节点扩展完毕后才进⾏下⼀层节点的扩展,直到得到⽬标节点为⽌。
这种搜索⽅式的优点是,只要存在有任何解答的话,它能保证最终找到由起始节点到⽬标节点的最短路径的解,但它的缺点是往往搜索过程很长。
2.2 深度优先搜索它是从根节点开始,⾸先扩展最新产⽣的节点,即沿着搜索树的深度发展下去,⼀直到没有后继结点处时再返回,换⼀条路径⾛下去。
就是在搜索树的每⼀层始终先只扩展⼀个⼦节点,不断地向纵深前进直到不能再前进(到达叶⼦节点或受到深度限制)时,才从当前节点返回到上⼀级节点,沿另⼀⽅向⼜继续前进。
这种⽅法的搜索树是从树根开始⼀枝⼀枝逐渐形成的。
由于⼀个有解的问题树可能含有⽆穷分枝,深度优先搜索如果误⼊⽆穷分枝(即深度⽆限),则不可能找到⽬标节点。
为了避免这种情况的出现,在实施这⼀⽅法时,定出⼀个深度界限,在搜索达到这⼀深度界限⽽且尚未找到⽬标时,即返回重找,所以,深度优先搜索策略是不完备的。
另外,应⽤此策略得到的解不⼀定是最佳解(最短路径)。
3 “⼋”数码难题的宽度优先搜索与深度优先搜索3.1“⼋”数码难题的宽度优先搜索步骤如下:1、判断初始节点是否为⽬标节点,若初始节点是⽬标节点则搜索过程结束;若不是则转到第2步;2、由初始节点向第1层扩展,得到3个节点:2、3、4;得到⼀个节点即判断该节点是否为⽬标节点,若是则搜索过程结束;若2、3、4节点均不是⽬标节点则转到第3步;3、从第1层的第1个节点向第2层扩展,得到节点5;从第1层的第2个节点向第2层扩展,得到3个节点:6、7、8;从第1层的第3个节点向第2层扩展得到节点9;得到⼀个节点即判断该节点是否为⽬标节点,若是则搜索过程结束;若6、7、8、9节点均不是⽬标节点则转到第4步;4、按照上述⽅法对下⼀层的节点进⾏扩展,搜索⽬标节点;直⾄搜索到⽬标节点为⽌。
广度优先搜索实例【例题】八数码难题(Eight-puzzle)。
在3X3的棋盘上,摆有 8个棋子,在每个棋子上标有1~8中的某一数字。
棋盘中留有一个空格。
空格周围的棋子可以移到空格中。
要求解的问题是,给出一种初始布局(初始状态)和目标布局(目标状态),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
初始状态和目标状态如下:初始状态目标状态 2 8 3 1 2 3 1 6 4 8 4 7 5 7 6 5求解本题我们可以分3步进行。
问题分析:由的解于题目要找是达到目标的最少步骤,因此可以这样来设计解题的方法:初始状态为搜索的出发点,把移动一步后的布局全部找到,检查是否有达到目标的布局,如果没有,再从这些移动一步的布局出发,找出移动两步后的所有布局,再判断是否有达到目标的。
依此类推,一直到某布局为目标状态为止,输出结果。
由于是按移动步数从少到多产生新布局的,所以找到的第一个目标一定是移动步数最少的一个,也就是最优解。
建立产生式系统:(1) 综合数据库。
用3X3的二维数组来表示棋盘的布局比较直观。
我们用Ch[i,j]表示第i 行第j列格子上放的棋子数字,空格则用0来表示。
为了编程方便,还需存储下面3个数据:该布局的空格位置(Si,Sj);初始布局到该布局的步数,即深度dep;以及该布局的上一布局,即父结点的位置(pnt)。
这样数据库每一个元素应该是由上述几个数据组成的记录。
在程序中,定义组成数据库元素的记录型为:Typenode=recordch:array[1..3,1..3] of byte;{存放某种棋盘布局}si,sj:byte; {记录此布局中空格位置}dep,pnt:byte;end;因为新产生的结点深度(从初始布局到该结点的步数)一般要比数据库中原有的结点深度大(或相等)。
按广度优先搜索的算法,深度大(步数多)的结点后扩展,所以新产生的结点应放在数据库的后面。
而当前扩展的结点从数据库前面选取,即处理时是按结点产生的先后次序进行扩展。
课程论文摘要八数码问题也称为九宫问题。
在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。
棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。
要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。
所谓问题的一个状态就是棋子在棋盘上的一种摆法。
棋子移动后,状态就会发生改变。
解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。
八数码问题一般使用搜索法来解。
搜索法有广度优先搜索法、深度优先搜索法、A*算法等。
这里详细讲述A*算法。
关键词:八数码问题;空间状态;搜索法;A*算法一、实验目的理解并熟悉掌握A*算法。
二、实验内容九宫格中有8个数码,其中只有一个空,规则是只能把一个数码移动到空的格子中,要求从一个初始状态移动到一个目标状态所要花费的最少步数三、问题的搜索形式描述(4要素)初始状态:8个数字将牌和空格在九宫格棋盘上的所有格局组成了问题的状态空间。
其中,状态空间中的任一种状态都可以作为初始状态。
后继函数:通过移动空格(上、下、左、右)和周围的任一棋子一次,到达新的合法状态。
目标测试:比较当前状态和目标状态的格局是否一致。
路径消耗:每一步的耗散值为1,因此整个路径的耗散值是从起始状态到目标状态的棋子移动的总步数。
四、解决方案介绍(原理)常用的状态空间搜索有深度优先和广度优先。
广度优先是从初始状态一层一层向下找,直到找到目标为止。
深度优先是按照一定的顺序前查找完一个分支,再查找另一个分支,以至找到目标为止。
广度和深度优先搜索有一个很大的缺陷就是他们都是在一个给定的状态空间中穷举。
这在状态空间不大的情况下是很合适的算法,可是当状态空间十分大,且不预测的情况下就不可取了。
由于八数码问题状态空间共有9!个状态,对于八数码问题如果选定了初始状态和目标状态,有9!/2个状态要搜索,考虑到时间和空间的限制,在这里采用A*算法作为搜索策略。
八数码——深度优先搜索八数码难题(Eight-puzzle)。
在3X3的棋盘上,摆有8个棋子,在每个棋子上标有1~8中的某一数字。
棋盘中留有一个空格。
空格周围的棋子可以移到空格中。
要求解的问题是,给出一种初始布局(初始状态)和目标布局(目标状态),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
初始状态和目标状态如下:初始状态目标状态2 83 1 2 31 6 4 8 47 5 7 6 5看到题目,我想到用0表示空格。
然后用深度优先搜索解决问题。
程序如下:typear=array[1..3,1..3]of integer;consts:ar=((2,8,3),(1,6,4),(7,0,5));z:ar=((1,2,3),(8,0,4),(7,6,5));fx:array[1..4,1..2]of integer=((-1,0),(1,0),(0,-1),(0,1));vara:array[1..100]of recordb:ar;end;function dd(a:ar):boolean;vari,j:integer;begindd:=true;for i:=1 to 3 dofor j:=1 to 3 doif a[i,j]<>z[i,j] thenexit(false);end;function pd(h,l,i:integer):boolean;beginpd:=true;if h+fx[i,1]=4 thenexit(false);if h+fx[i,1]=0 thenexit(false);if l+fx[i,2]=0 thenexit(false);if l+fx[i,2]=4 thenexit(false);end;function fan(n:integer):integer;begincase n of1:exit(2);2:exit(1);3:exit(4);4:exit(3);end;end;procedure p(n:integer);vari,j,k:integer;beginfor i:=1 to n dobeginfor j:=1 to 3 dobeginfor k:=1 to 3 dowrite(a[i].b[j,k],' ');writeln;end;writeln;end;end;procedure dfs(dep,h,l,f:integer);varj,x,y:integer;beginif dd(a[dep].b) thenbeginp(dep);close(output);halt;end;for j:=1 to 4 doif pd(h,l,j)and(j<>f) thenbeginx:=h+fx[j,1];y:=l+fx[j,2];a[dep+1]:=a[dep];a[dep+1].b[h,l]:=a[dep].b[x,y];a[dep+1].b[x,y]:=0;if dep<6 thendfs(dep+1,x,y,fan(j));end;end;beginassign(output,'bsm.out');rewrite(output);a[1].b:=s;dfs(1,3,2,0);end.运行程序,输出:2 8 31 6 47 0 52 8 31 0 47 6 52 0 31 8 47 6 50 2 31 8 47 6 51 2 30 8 47 6 51 2 38 0 47 6 5这个程序对是对了。
实验报告一、实验问题八数码问题求解二、实验软件VC6.0 编程语言或其它编程语言三、实验目的1. 熟悉人工智能系统中的问题求解过程;2. 熟悉状态空间的盲目搜索和启发式搜索算法的应用;3. 熟悉对八数码问题的建模、求解及编程语言的应用。
四、实验数据及步骤(一、)实验内容八数码问题:在3 ×3 的方格棋盘上,摆放着1 到8 这八个数码,有1 个方格是空的,其初始状态如图1 所示,要求对空格执行空格左移、空格右移、空格上移和空格下移这四个操作使得棋盘从初始状态到目标状态。
2 83 1 2 31 4 8 47 6 5 7 6 5(a) 初始状态(b) 目标状态图1 八数码问题示意图(二、)基本数据结构分析和实现1. 结点状态我采用了struct Node 数据类型typedef struct _Node{int digit[ROW][COL];int dist; // distance between one state and the destination 个表和目的表的距离int dep; // the depth of node 深度// So the comment function = dist + dep. 估价函数值int index; // point to the location of parent 父节点的位置} Node; 2. 发生器函数定义的发生器函数由以下的四种操作组成:(1) 将当前状态的空格上移Node node_up;Assign(node_up, index);// 向上扩展的节点int dist_up = MAXDISTANCE;(2) 将当前状态的空格下移Node node_down;Assign(node_down, index);// 向下扩展的节点int dist_down = MAXDISTANCE;(3) 将当前状态的空格左移Node node_left;Assign(node_left, index);// 向左扩展的节点int dist_left = MAXDISTANCE;(4) 将当前状态的空格右移Node node_right;Assign(node_right, index);// 向右扩展的节点int dist_right = MAXDISTANCE;通过定义结点状态和发生器函数,就解决了8 数码问题的隐式图的生成问题。
基于启发式搜索的九宫图问题求解及其界面设计一、九宫图问题简介九宫图问题又称八数码问题,在3×3的九宫格棋盘上,每一个将牌都刻有1—8中的某一个数码。
棋盘中留有一个空格,允许其周围的某一个将牌向空格移动,这样通过移动将牌就可以不断改变将牌的布局。
给定一种初始的将牌布局(称初始状态)和一个目标布局(称目标状态),如何移动将牌,实现从初始状态到目标状态的转变。
问题的解答也就是给出一个合法的走步序列。
二、求解算法简介1.解的存在性分析九宫图问题的状态空间共有9!个状态,对于给定的初始状态和目标状态,有9!/2个状态要搜索,九宫图问题不一定能进行求解,因此对于给定的初始状态和目标状态需首先进行解存在性判断。
引入线性代数中逆序数的概念:在一个排列中,如果一对数的前后位置和大小顺序相反,即前面的数大于后面的数,那么它们就成为一个逆序,排列中逆序的总数就称为这个排列的逆序数。
只有当初始状态的排列逆序数与目标状态的排列逆序数奇偶性相同时,初始状态才能通过移动到达目标状态。
2.A*算法简介本文选用A*算法进行九宫图问题求解。
A*算法是一种静态路网中求解最短路径最有效的直接搜索方法。
A*算法结合了深度优先算法和广度优先,是一种启发式搜索算法。
评估函数为:f(n)=g(n)+h(n),f(n)表示从初始状态经由状态n 到目标状态的估计,g(n)是在状态空间中从初始状态到状态n 的实际代价,h(n)是从状态n 到目标状态的最佳路径的估计代价,本文算法中g(n)为从初始状态到当前状态的深度,h (n )为节点n 的每一数码与其目标位置之间的曼哈顿距离之和,可表示为:81()(||||)ni ti ni ti i h n x x y y ==-+-∑其中ni x 为n 状态下数字i 所在的行,ti x 为目标状态数字i 所在行,ni y 表示n 状态下数字i 所在列,ti y 表示目标状态数字i 所在列。
3.搜索动作空间简介空格移动空间为{上、下、左、右},但并非所有位置均能进行全空间搜索,判断空格所在位置,根据其所在位置对其搜索动作进行限制,其动作空间如表1所示。
启发式搜索⼋数码问题启发式搜索1. 介绍⼋数码问题也称为九宫问题。
在3×3的棋盘,摆有⼋个棋⼦,每个棋⼦上标有1⾄8的某⼀数字,不同棋⼦上标的数字不相同。
棋盘上还有⼀个空格(以数字0来表⽰),与空格相邻的棋⼦可以移到空格中。
要求解决的问题是:给出⼀个初始状态和⼀个⽬标状态,找出⼀种从初始转变成⽬标状态的移动棋⼦步数最少的移动步骤。
所谓问题的⼀个状态就是棋⼦在棋盘上的⼀种摆法。
解⼋数码问题实际上就是找出从初始状态到达⽬标状态所经过的⼀系列中间过渡状态。
2. 使⽤启发式搜索算法求解8数码问题。
1) A ,A 星算法采⽤估价函数()()()()w n f n d n p n ??=+,其中:()d n 是搜索树中结点n 的深度;()w n 为结点n 的数据库中错放的棋⼦个数;()p n 为结点n 的数据库中每个棋⼦与其⽬标位置之间的距离总和。
2)宽度搜索采⽤f(i)为i 的深度,深度搜索采⽤f(i)为i 的深度的倒数。
3. 算法流程①把起始节点S 放到OPEN 表中,并计算节点S 的)(S f ;②如果OPEN 是空表,则失败退出,⽆解;③从OPEN 表中选择⼀个f 值最⼩的节点i 。
如果有⼏个节点值相同,当其中有⼀个为⽬标节点时,则选择此⽬标节点;否则就选择其中任⼀个节点作为节点i ;④把节点i 从 OPEN 表中移出,并把它放⼊ CLOSED 的已扩展节点表中;⑤如果i 是个⽬标节点,则成功退出,求得⼀个解;⑥扩展节点i ,⽣成其全部后继节点。
对于i 的每⼀个后继节点j :计算)(j f ;如果j 既不在OPEN 表中,⼜不在CLOCED 表中,则⽤估价函数f 把它添⼊OPEN 表中。
从j 加⼀指向其⽗节点i 的指针,以便⼀旦找到⽬标节点时记住⼀个解答路径;如果j 已在OPEN 表或CLOSED 表中,则⽐较刚刚对j 计算过的f 和前⾯计算过的该节点在表中的f 值。
如果新的f 较⼩,则(I)以此新值取代旧值。
八数码问题详解(用bfs实现)注:下面要介绍的方法是LRJ大神首创的,笔者只是在巨人的肩膀上归纳总结了一下,有错误的还希望众大牛指点,本人定将不胜感激。
八数码问题也称为九宫问题。
在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。
棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。
要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。
所谓问题的一个状态就是棋子在棋盘上的一种摆法。
棋子移动后,状态就会发生改变。
解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。
如图:对这道题用bfs解决个人认为是个不错的方法,首先因为它有九个格子,那么便可以用state 数组记录他的的九个格子的数值,其中空格为0,后者由于它只有九个数,因此共有9!=362880种可能性,用bfs解决较快。
下面给出bfs实现的三种代码(这里的三种指的是对是否访问过已知节点的三种判断方法): 1.//用一套排列的编码和解码函数解决同一状态的再次访问//用统一的编码与解码函数避免同种状态的再次出现#include <stdio.h>#include <string.h>#include <stdlib.h>#include <math.h>#define len 362888 //状态共有362880种,数组稍微开大点#define le 9 //每种状态有9个数据,也可看为每种状态下又有9种状态typedefint state[le]; //状态:表示九个格子state st[len],goal; //st为状态数组goal为目标数组int dis[len],fact[le],head[len],vis[len],der[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; //dis为每种状态的已走的步骤//der为方向:上,下,左,右void encode(){ //编码int i;for(i=fact[0]=1;i<le;i++)fact[i]=fact[i-1]*i;}int decode(int s){ //解码inti,j,code,cnt;for(i=code=0;i<le;i++){for(cnt=0,j=i+1;j<le;j++)if(st[s][i]>st[s][j])cnt++;code+=cnt*fact[8-i];}if(vis[code]) return 0;else return vis[code]=1;}intbfs(){int front=1,rear=2,i,x,y,z,nx,ny,nz;encode();while(front<rear){state& s=st[front];if(memcmp(s,goal,sizeof(s))==0) //对front状态和目标状态进行比较return front;for(i=0;i<le;i++) //找到为0的元素,即空的那个格子,这里选取空的那个格子是应为相对于1,2,3,...8这样的数据,0作为判断依据简单于用数据作为判断依据if(s[i]==0)break;x=i/3; y=i%3; z=i; //记录空的格子的行标,列表,和所在位置,这里的位置按照从左到右从上到下依次递增for(i=0;i<4;i++){ //按照上,下,左,右四个方向进行搜索nx=x+der[i][0];ny=y+der[i][1];nz=nx*3+ny;if(nx>=0&&nx<3&&ny>=0&&ny<3){state& t=st[rear];memcpy(&t,&s,sizeof(s)); //记录此时的状态即九个格子的数值t[z]=s[nz]; t[nz]=s[z];dis[rear]=dis[front]+1;if(decode(rear)) //判断st[rear]这种状态是否已经出现过rear++;}}front++;}return 0;}int main(void){intncase,i,oj;scanf("%d",&ncase);while(ncase--){memset(head,0,sizeof(head));memset(vis,0,sizeof(vis));for(i=0;i<le;i++) scanf("%d",&st[1][i]); //按从左到右从上到下的顺序存储数据for(i=0;i<le;i++) scanf("%d",&goal[i]);oj=bfs();if(oj)printf("%d\n",dis[oj]);elseputs("-1");}return 0;}2.//用hash避免同一状态的再次访问在讲这种方法之前建议读者先看一下算法导论中有关hash的介绍为了方便自己以后看的时候方便,先写一下有关hash的内容Hash表解决冲突三种hash函数:first:除法散列函数: h(k)=k mod m(m为所选的余数,最好选接近装载因子α=n/m,但又远离2的k次幂的质数)second:乘法散列法函数: 看图:具体代码实现下面有介绍third:全域散列函数h a,b(k)=((ak+b) mod p) mod m (p>m 且p和m都为质数)//用链表实现hash#include <stdio.h>#include <string.h>#include <stdlib.h>#include <math.h>#define len 362888 //状态共有362880种,数组稍微开大点#define le 9 //每种状态有9个数据,也可看为每种状态下又有9种状态typedefint state[le]; //状态:表示九个格子state st[len],goal; //st为状态数组goal为目标数组int dis[len],der[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; //dis为每种状态的已走的步骤//der为方向:上,下,左,右typedefstruct node {int v;struct node *next;}ore;ore *head[len]; //这里的head为hash表ore * create_new_node(){ore *p;p=(ore *)calloc(1,sizeof(ore));p->next=NULL;return p;}//此处为哈希函数,不理解的建议看一下算法导论,下面用3种hash函数实现//first:除法散列法int hash(state& s){inti,num,m=372001; //m为所选的余数,最好选接近装载因子α=n/m,但又远离2的k次幂的质数for(i=num=0;i<le;i++)num=num*10+s[i];returnnum%m;}//second:乘法散列法int hash(state& s){inti,num;long longk,w=32,ss,r0,p=14,ans; //这里的w为需要截取的位数//p为要截取的数字长度const double A = (sqrt(5.)-1)/2;for(i=num=0;i<le;i++)num=num*10+s[i];k=(long long)num;ss=(long long)(A*(1LL<<w));r0=k*ss%(1LL<<w);ans=r0>>(w-p);returnans;}//third:全域散列hash functionint hash(state& s){inti,num;longlong a=3,b=4,m=350001,p=360001,k,ans;for(i=num=0;i<le;i++)num=num*10+s[i];k=(long long)num;ans=(a*k+b)%p%m; //此处为全域散列函数returnans;}//以上的三种hash function选取一种即可bool find(int s){int h;ore *u,*p;h=hash(st[s]); //通过hash function计算出hash值,并将该元素定义为head数组的下标u=create_new_node();if(!head[h]) //如果head[h]未创建,即未访问过,则创建一个新节点head[h]=create_new_node();u=head[h]->next; //u指向head[h]的下一个元素while(u){if(memcmp(st[u->v],st[s],sizeof(st[s]))==0) //如果找到memcmp(st[u->v],st[s],sizeof(st[s]))==0 的数据项则说明该节点已经访问过return false;u=u->next; //访问下一个节点//原理看下面的说明}p=create_new_node(); //创建一个新节点p->next=head[h]->next; //用头插法在散列表中插入新的节点head[h]->next=p;p->v=s;return true;}intbfs(){int front=1,rear=2,i,x,y,z,nx,ny,nz;while(front<rear){state& s=st[front];if(memcmp(s,goal,sizeof(s))==0) //对front状态和目标状态进行比较return front;for(i=0;i<le;i++) //找到为0的元素,即空的那个格子,这里选取空的那个格子是应为相对于1,2,3,...8这样的数据,0作为判断依据简单于用数据作为判断依据if(s[i]==0)break;x=i/3; y=i%3; z=i; //记录空的格子的行标,列表,和所在位置,这里的位置按照从左到右从上到下依次递增for(i=0;i<4;i++){ //按照上,下,左,右四个方向进行搜索nx=x+der[i][0];ny=y+der[i][1];nz=nx*3+ny;if(nx>=0&&nx<3&&ny>=0&&ny<3){state& t=st[rear];memcpy(&t,&s,sizeof(s)); //记录此时的状态即九个格子的数值t[z]=s[nz]; t[nz]=s[z];dis[rear]=dis[front]+1;if(find(rear)) //判断st[rear]这种状态是否已经出现过rear++;}}front++;}return 0;}int main(void){intncase,i,oj;scanf("%d",&ncase);while(ncase--){memset(head,0,sizeof(head));for(i=0;i<le;i++) scanf("%d",&st[1][i]); //按从左到右从上到下的顺序存储数据for(i=0;i<le;i++) scanf("%d",&goal[i]);oj=bfs();if(oj)printf("%d\n",dis[oj]);elseputs("-1");}return 0;}//基于链表的用数组实现hash#include <stdio.h>#include <string.h>#include <stdlib.h>#include <math.h>#define len 362888 //状态共有362880种,数组稍微开大点#define le 9 //每种状态有9个数据,也可看为每种状态下又有9种状态typedefint state[le]; //状态:表示九个格子state st[len],goal; //st为状态数组goal为目标数组int dis[len],head[len],next[len],der[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; //dis为每种状态的已走的步骤//head为哈希表//next为链表//der为方向:上,下,左,右//此处为哈希函数,不理解的建议看一下算法导论,下面用3种hash函数实现//first:除法散列法int hash(state& s){inti,num,m=372001; //m为所选的余数,最好选接近装载因子α=n/m,但又远离2的k次幂的质数for(i=num=0;i<le;i++)num=num*10+s[i];returnnum%m;}//second:乘法散列法int hash(state& s){inti,num;long longk,w=32 /*这里的w为需要截取的位数*/ ,ss,r0,p=14 /*p为要截取的数字长度*/ ,ans;const double A = (sqrt(5.)-1)/2;for(i=num=0;i<le;i++)num=num*10+s[i];k=(long long)num;ss=(long long)(A*(1LL<<w));r0=k*ss%(1LL<<w);ans=r0>>(w-p);returnans;}//third:全域散列hash functionint hash(state& s){inti,num;longlong a=3,b=4,m=350001,p=360001,k,ans;for(i=num=0;i<le;i++)num=num*10+s[i];k=(long long)num;ans=(a*k+b)%p%m; //此处为全域散列函数returnans;}//以上的三种hash function选取一种即可bool find(int s){inth,u;h=hash(st[s]); //通过hash function计算出hash值,并将该元素定义为head数组的下标u=head[h]; //通过u获得head[h]的值while(u){ //如果前面已经访问过该项数据,则说明数据已经插入该项所对应的next数组中,则继续访问if(memcmp(st[u],st[s],sizeof(st[s]))==0) //如果找到memcmp(st[u],st[s],sizeof(st[s]))==0 的数据项则说明该节点已经访问过return false;u=next[u]; //访问下一个节点//原理看下面的说明}//这里的next其实是一个个链表的集合所组成的数组,不用链表的原因是应为链表的创建需要耗时,而且还要有多余的空间存储指针next[s]=head[h]; //这里的原理实际上是基于链表的头插法head[h]=s;return true;}intbfs(){int front=1,rear=2,i,x,y,z,nx,ny,nz;while(front<rear){state& s=st[front];if(memcmp(s,goal,sizeof(s))==0) //对front状态和目标状态进行比较return front;for(i=0;i<le;i++) //找到为0的元素,即空的那个格子,这里选取空的那个格子是应为相对于1,2,3,...8这样的数据,0作为判断依据简单于用数据作为判断依据if(s[i]==0)break;x=i/3; y=i%3; z=i; //记录空的格子的行标,列表,和所在位置,这里的位置按照从左到右从上到下依次递增for(i=0;i<4;i++){ //按照上,下,左,右四个方向进行搜索nx=x+der[i][0];ny=y+der[i][1];nz=nx*3+ny;if(nx>=0&&nx<3&&ny>=0&&ny<3){state& t=st[rear];memcpy(&t,&s,sizeof(s)); //记录此时的状态即九个格子的数值t[z]=s[nz]; t[nz]=s[z];dis[rear]=dis[front]+1;if(find(rear)) //判断st[rear]这种状态是否已经出现过rear++;}}front++;}return 0;}int main(void){intncase,i,oj;scanf("%d",&ncase);while(ncase--){memset(head,0,sizeof(head));memset(next,0,sizeof(next));for(i=0;i<le;i++) scanf("%d",&st[1][i]); //按从左到右从上到下的顺序存储数据for(i=0;i<le;i++) scanf("%d",&goal[i]);oj=bfs();if(oj)printf("%d\n",dis[oj]);elseputs("-1");}return 0;}3.用stl集合避免重复访问同一状态//用stl避免同一状态重复出现#include <stdio.h>#include <string.h>#include <stdlib.h>#include <math.h>#include <iostream>#include <set>using namespace std;#define len 362888 //状态共有362880种,数组稍微开大点#define le 9 //每种状态有9个数据,也可看为每种状态下又有9种状态typedefint state[le]; //状态:表示九个格子state st[len],goal; //st为状态数组goal为目标数组int dis[len],head[len],der[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; //dis为每种状态的已走的步骤//der为方向:上,下,左,右structcmp{bool operator()(inta,int b)const{returnmemcmp(&st[a],&st[b],sizeof(st[a]))<0;}};set<int,cmp>vis;voidinit_lookup_table(){vis.clear();}inttry_to_insert(int s){if(vis.count(s)) return 0;vis.insert(s);return 1;}intbfs(){int front=1,rear=2,i,x,y,z,nx,ny,nz;init_lookup_table();while(front<rear){state& s=st[front];if(memcmp(s,goal,sizeof(s))==0) //对front状态和目标状态进行比较return front;for(i=0;i<le;i++) //找到为0的元素,即空的那个格子,这里选取空的那个格子是应为相对于1,2,3,...8这样的数据,0作为判断依据简单于用数据作为判断依据if(s[i]==0)break;x=i/3; y=i%3; z=i; //记录空的格子的行标,列表,和所在位置,这里的位置按照从左到右从上到下依次递增for(i=0;i<4;i++){ //按照上,下,左,右四个方向进行搜索nx=x+der[i][0];ny=y+der[i][1];nz=nx*3+ny;if(nx>=0&&nx<3&&ny>=0&&ny<3){state& t=st[rear];memcpy(&t,&s,sizeof(s)); //记录此时的状态即九个格子的数值t[z]=s[nz]; t[nz]=s[z];dis[rear]=dis[front]+1;if(try_to_insert(rear)) //判断st[rear]这种状态是否已经出现过rear++;}}front++;}return 0;}int main(void){intncase,i,oj;scanf("%d",&ncase);while(ncase--){memset(head,0,sizeof(head));for(i=0;i<le;i++) scanf("%d",&st[1][i]); //按从左到右从上到下的顺序存储数据for(i=0;i<le;i++) scanf("%d",&goal[i]);oj=bfs();if(oj)printf("%d\n",dis[oj]);elseputs("-1");}return 0;}。
人工智能实验报告学院:信息科学与工程学院班级:自动化0901班学号: 06姓名:孙锦岗指导老师:刘丽珏日期:2011年12月20日一、实验名称、目的及内容实验名称:八数码难题的搜索求解演示实验目的:加深对图搜索策略概念的理解,掌握搜索算法。
实验内容要求:以八数码难题为例演示广度优先或深度优先搜索、A算法(本实验使用的是广度优先搜索)的搜索过程,争取做到直观、清晰地演示算法。
八数码难题:在3×3方格棋盘上,分别放置了标有数字1,2,3,4,5,6,7,8的八张牌,初始状态S0,目标状态如图所示,可以使用的操作有:空格上移,空格左移,空格右移,空格下移。
试编一程序实现这一搜索过程。
二、实验原理及基本技术路线图实验原理:八数码问题中,程序产生的随机排列转换成目标共有两种可能,而且这两种不可能同时成立,也就是奇数排列和偶数排列。
我们可以把一个随机排列的数组从左到右从上到下用一个数组表示,例如{8,7,1,5,2,6,3,4,0}其中0代表空格。
它在奇序列位置上。
在这个数组中我们首先计算它能够重排列出来的结果,公式就是:∑(F(X))=Y,其中F(X),就是一个数他前面比这个数小的数的个数,Y为奇数和偶数个有一种解法。
那么上面的数组我们就可以解出它的结果。
数据结构:本实验使用的数据结构是队列,应用队列先进先出的特点来实现对节点的保存和扩展。
首先建立一个队列,将初始结点入队,并设置队列头和尾指,然后取出队列(头指针所指)的结点进行扩展,从它扩展出子结点,并将这些结点按扩展的顺序加入队列,然后判断扩展出的新结点与队列中的结点是否重复,如果重复则,否则记录其父结点,并将它加入队列,更新队列尾指针,然后判断扩展出的结点是否是目标结点,如果是则显示路径,程序结束。
否则如果队列头的结点可以扩展,直接返回第二步。
否则将队列头指针指向下一结点,再返回第二步,知道扩展出的结点是目标结点结束,并显示路径。
算法分析:九宫问题的求解方法就是交换空格(0)位置,直至到达目标位置为止。