八数码问题及A*算法

一.八数码问题
八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。
所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。
八数码问题一般使用搜索法来解。
搜索法有广度优先搜索法、深度优先搜索法、A*算法等。这里通过用不同方法解八数码问题来比较一下不同搜索法的效果。

二.搜索算法基类
1.八数码问题的状态表示
八数码问题的一个状态就是八个数字在棋盘上的一种放法。每个棋子用它上面所标的数字表示,并用0表示空格,这样就可以将棋盘上棋子的一个状态存储在一个一维数组p[9]中,存储的顺序是从左上角开始,自左至右,从上到下。也可以用一个二维数组来存放。
2.结点
搜索算法中,问题的状态用结点描述。结点中除了描述状态的数组p[9]外,还有一个父结点指针last,它记录了当前结点的父结点编号,如果一个结点v是从结点u经状态变化而产生的,则结点u就是结点v的父结点,结点v的last记录的就是结点u的编号。在到达目标结点后,通过last 可以找出搜索的路径。
3.类的结构
在C++中用类来表示结点,类将结点有关的数据操作封装在一起。
不同的搜索算法具有一定共性,也有各自的个性,因此这里将不同搜索算法的共有的数据和功能封装在一个基类中,再通过继承方式实现不同的搜索算法。
4.结点扩展规则
搜索就是按照一定规则扩展已知结点,直到找到目标结点或所有结点都不能扩展为止。
八数码问题的结点扩展应当遵守棋子的移动规则。按照棋子移动的规则,每一次可以将一个与空格相邻棋子移动到空格中,实际上可以看作是空格作相反移动。空格移动的方向可以是右、下、左、上,当然不能移出边界。
棋子的位置,也就是保存状态的数组元素的下标。空格移动后,它的位置发生变化,在不移出界时,空格向右、下、左和上移动后,新位置是原位置分别加上1、3、-1、-3,如果将空格向右、下、左和上移动分别用0、1、2、3表示,并将-3、3、-1、1放在静态数组d[4]中,空格位置用spac表示,那么空格向方向i移动后,它的位置变为spac+d[i]。
空格移动所产生的状态变化,反映出来则是将数组p[]中,0的新位置处的数与0交换位置。
5.八数码问题的基类

八数码问题的基类及其成员函数的实现如下:

View Code

数据文件的结构:
一共三行,第一行是用空格隔开的九个数字0~8,这是初始状态。第二行是一个数字,空格(数字0)的位置,第三行也是用空格隔开的九个数字0~8,这是目标状态。

三.线性表
搜索法在搜索过程中,需要使用一个队列存储搜索的中间结点,为了在找到目标结点后,能够找到从初始结点到目标结点的路径,需要保留所有搜索过的结点。另一方面,不同问题甚至同一问题的不同搜索方法中,需要存储的结点数量相差很大,所以这里采用链式线性表作为存储结构,同时,为适应不同问题,线性表设计成类模板形式。

View Code

线性表单独以头文件形式存放。

四.广度优先搜索法
在搜索法中,广度优先搜索法是寻找最短路经的首选。
1.广度优先搜索算法的基本步骤
1)建立一个队列,将初始结点入队,并设置队列头和尾指针
2)取出队列头(头指针所指)的结点进行扩展,从它扩展出子结点,并将这些结点按扩展的顺序加入队列。 
3)如果扩展出的新结点与队列中的结点重复,则抛弃新结点,跳至第六步。
4)如果扩展出的新结点与队列中的结点不重复,则记录其父结点,并将它加入队列,更新队列尾指针。
5)如果扩展出的结点是目标结点,则输出路径,程序结束。否则继续下一步。
6)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,再返回第二步。
2.搜索路径的输出
搜索到目标结点后,需要输出搜索的路径。每个结点有一个数据域last,它记录了结点的父结点,因此输出搜索路径时,就是从目标结点Q出发,根据last找到它的父结点,再根据这个结点的last找到它的父结点,....,最后找到初始结点。搜索的路径就是从初始结点循相反方向到达目标结点的路径。
3.广度优先搜索法TBFS类的结构
广度优先搜索法TBFS类是作为TEight类的一个子类。其类的结构和成员函数的实现如下:

View Code

4.广度优先搜索法的缺点
广度优先搜索法在有解的情形总能保证搜索到最短路经,也就是移动最少步数的路径。但广度优先搜索法的最大问题在于搜索的结点数量太多,因为在广度优先搜索法中,每一个可能扩展出的结点都是搜索的对象。随着结点在搜索树上的深度增大,搜索的结点数会很快增长,并以指数形式扩张,从而所需的存储空间和搜索花费的时间也会成倍增长。

五.双向广度优先搜索法
1.双向广度优先搜索法
八数码问题具有可逆性,也就是说,如果可以从一个状态A扩展出状态B,那么同样可以从状态B扩展出状态A,这种问题既可以从初始状态出发,搜索目标状态,也可以从目标状态出发,搜索初始状态。对这类问题如果采用双向广度优先搜索法,将可以大大节省搜索的时间。
所谓双向广度优先搜索法,是同时从初始状态和目标状态出发,采用广度优先搜索的策略,向对方搜索,如果问题存在解,则两个方向的搜索会在中途相遇,即搜索到同一个结点。将两个方向的搜索路径连接起来,就可以得到从初始结点到目标结点的搜索路径。
2.双向广度优先搜索算法
双向广度优先搜索算法的基本步骤如下:
1)建立两个队列,一个是正向搜索的队列,另一个是反向搜索的队列。将初始结点放入正向队列,将目标结点放入反向队列,并设置两个队列的头和尾指针。
2)从正向队列取出队列头(头指针所指)的结点进行扩展。
3)如果扩展出的新结点与队列中的结点重复,则抛弃新结点,跳至第六步。
4)如果扩展出的新结点与队列中的结点不重复,则记录其父结点,并将它加入队列,更新队列尾指针。
5)检查扩展出的结点是否在另一方向的队列中,如果是则两个方向的搜索相遇,显示搜索路径,程序结束。否则继续下一步。
6)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,然后对另一方向搜索的队列,按照第二步开始的同样步骤处理。
3.双向广度优先搜索法的优势
广度优先搜索法搜索时,结点不断扩张,深度越大,结点数越多。如果从两个方向向对方搜索,就会在路径中间某个地方相会,这样,双方的搜索的深度都不大,所搜索过的结点数就少得多,搜索时间也就节省不少。
从理论上说,如果每一结点可扩展的子结点数为m,广度优先搜索的搜索树就是一颗m叉树,也就是每个结点都由m个分支。按完全m叉树计算,如果目标结点在第n层,广度优先搜索就必须在搜索树上扩展完n-1层的所有结点,扩展的结点数为m(mn-1)/(m-1)。对于双向广度优先搜索来说,如果两个方向的搜索在第i层生成同一子结点,那么正向搜索扩展的结点数为m(mi-1)/(m-1),反向搜索扩展的结点数为m(mn-i-1)/(m-1),搜索的结点总数为m(mi+mn-i-1)/(m-1)(其中n是最优解路径长度,i=(m+1) div 2,)。设n为偶数(n=2*i),广度优先双向搜索扩展的结点数约是广度优先搜索的2/(mi/2+1)*100%,相对减少(mi/2-1)/(mi/2+1)*100%。
4.判断两个方向的搜索相遇
在双向广度优先搜索法中,如何判断两个方向的搜索相遇呢?只要我们在生成结点的同时,判断该结点是否出现在相反方向的搜索树上即可,也就是说,在某个方向搜索中扩展出一个新结点,如果它与另一个方向已扩展出的结点重复,也就找到了解。
5.双向广度优先搜索法的TDBFS类结构
双向广度优先搜索法的TDBFS和广度优先搜索法类似,也是TEight类的子类,类结构及其成员函数的实现如下:

View Code

六.A*算法
1.启发式搜索
广度优先搜索和双向广度优先搜索都属于盲目搜索,这在状态空间不大的情况下是很合适的算法,可是当状态空间十分庞大时,它们的效率实在太低,往往都是在搜索了大量无关的状态结点后才碰到解答,甚至更本不能碰到解答。
搜索是一种试探性的查寻过程,为了减少搜索的盲目性引,增加试探的准确性,就要采用启发式搜索了。所谓启发式搜索就是在搜索中要对每一个搜索的位置进行评估,从中选择最好、可能容易到达目标的位置,再从这个位置向前进行搜索,这样就可以在搜索中省略大量无关的结点,提高了效率。
2.A*算法
A*算法是一种常用的启发式搜索算法。
在A*算法中,一个结点位置的好坏用估价函数来对它进行评估。A*算法的估价函数可表示为: 
f'(n) = g'(n) + h'(n) 
这里,f'(n)是估价函数,g'(n)是起点到终点的最短路径值(也称为最小耗费或最小代价),h'(n)是n到目标的最短路经的启发值。由于这个f'(n)其实是无法预先知道的,所以实际上使用的是下面的估价函数:
f(n) = g(n) + h(n) 
其中g(n)是从初始结点到节点n的实际代价,h(n)是从结点n到目标结点的最佳路径的估计代价。在这里主要是h(n)体现了搜索的启发信息,因为g(n)是已知的。用f(n)作为f'(n)的近似,也就是用g(n)代替g'(n),h(n)代替h'(n)。这样必须满足两个条件:(1)g(n)>=g'(n)(大多数情况下都是满足的,可以不用考虑),且f必须保持单调递增。(2)h必须小于等于实际的从当前节点到达目标节点的最小耗费h(n)<=h'(n)。第二点特别的重要。可以证明应用这样的估价函数是可以找到最短路径的。
3.A*算法的步骤
A*算法基本上与广度优先算法相同,但是在扩展出一个结点后,要计算它的估价函数,并根据估价函数对待扩展的结点排序,从而保证每次扩展的结点都是估价函数最小的结点。
A*算法的步骤如下:
1)建立一个队列,计算初始结点的估价函数f,并将初始结点入队,设置队列头和尾指针。
2)取出队列头(队列头指针所指)的结点,如果该结点是目标结点,则输出路径,程序结束。否则对结点进行扩展。 
3)检查扩展出的新结点是否与队列中的结点重复,若与不能再扩展的结点重复(位于队列头指针之前),则将它抛弃;若新结点与待扩展的结点重复(位于队列头指针之后),则比较两个结点的估价函数中g的大小,保留较小g值的结点。跳至第五步。
4)如果扩展出的新结点与队列中的结点不重复,则按照它的估价函数f大小将它插入队列中的头结点后待扩展结点的适当位置,使它们按从小到大的顺序排列,最后更新队列尾指针。
5)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,再返回第二步。
4.八数码问题的A*算法的估价函数
估价函数中,主要是计算h,对于不同的问题,h有不同的含义。那么在八数码问题中,h的含意是各什么?八数码问题的一个状态实际上是数字0~8的一个排列,用一个数组p[9]来存储它,数组中每个元素的下标,就是该数在排列中的位置。例如,在一个状态中,p[3]=7,则数字7的位置是3。如果目标状态数字3的位置是8,那么数字7对目标状态的偏移距离就是3,因为它要移动3步才可以回到目标状态的位置。
八数码问题中,每个数字可以有9个不同的位置,因此,在任意状态中的每个数字和目标状态中同一数字的相对距离就有9*9种,可以先将这些相对距离算出来,用一个矩阵存储,这样只要知道两个状态中同一个数字的位置,就可查出它们的相对距离,也就是该数字的偏移距离:
       0 1 2 3 4 5 6 7 8
0      0 1 2 1 2 3 2 3 4
1      1 0 1 2 1 2 3 2 3
2      2 1 0 3 2 1 4 3 2
3      1 2 3 0 1 2 1 2 3
4      2 1 2 1 0 1 2 1 2
5      3 2 1 2 1 0 3 2 1
6      2 3 4 1 2 3 0 1 2
7      3 2 3 2 1 2 1 0 1
8      4 3 2 3 2 1 2 1 0
例如在一个状态中,数字8的位置是3,在另一状态中位置是7,那么从矩阵的3行7列可找到2,它就是8在两个状态中的偏移距离。
估价函数中的h就是全体数字偏移距离之和。
显然,要计算两个不同状态中同一数字的偏移距离,需要知道该数字在每个状态中的位置,这就要对数组p[9]进行扫描。由于状态发生变化,个数字的位置也要变化,所以每次计算h都沿线扫描数组,以确定每个数字在数组中的位置。为了简化计算,这里用一个数组存储状态中各个数字的位置,并让它在状态改变时随着变化,这样就不必在每次计算h时,再去扫描状态数组。
例如,某个状态中,数字5的位置是8,如果用数组r[9]存储位置,那么就有r[5]=8。
现在用数组r[9]存储当前状态的数字位置,而用s[9]存储目标状态的数字位置,那么当前状态数字i对目标状态的偏移距离就是矩阵中r[i]行s[i]列对应的值。
5.A*算法的类结构
A*算法的类声明如下:

View Code

七.测试程序

A*算法的测试:

View Code

eight.txt文件中的数据(初始态和目标态):
一共三行,第一行是用空格隔开的九个数字0~8,这是初始状态。第二行是一个数字,空格(数字0)的位置,第三行也是用空格隔开的九个数字0~8,这是目标状态。

8 3 5 1 2 7 4 6 0
8
1 2 3 4 5 6 7 8 0

eight_dis.txt中的数据(估计函数使用):
0 1 2 1 2 3 2 3 4
1 0 1 2 1 2 3 2 3
2 1 0 3 2 1 4 3 2
1 2 3 0 1 2 1 2 3
2 1 2 1 0 1 2 1 2
3 2 1 2 1 0 3 2 1
2 3 4 1 2 3 0 1 2
3 2 3 2 1 2 1 0 1

4 3 2 3 2 1 2 1 0

八.算法运行结果
1.BFS算法只能适用于到达目标结点步数较少的情况,如果步数超过15步,运行时间太长,实际上不再起作用。
2.对于随机生成的同一个可解状态,BFS算法最慢,DBFS算法较慢,A*算法较快。但在15步以内,DBFS算法与A*算法相差时间不大,超过15步后,随步数增加,A*算法的优势就逐渐明显,A*算法要比DBFS算法快5倍以上,并随步数增大而增大。到25步以上,DBFS同样因运行时间过长而失去价值。
3.一般来说,解答的移动步数每增加1,程序运行时间就要增加5倍以上。由于八数码问题本身的特点,需要检查的节点随步数增大呈指数形式增加,即使用A*算法,也难解决移动步数更多的问题。

九.问题可解性
八数码问题的一个状态实际上是0~9的一个排列,对于任意给定的初始状态和目标,不一定有解,也就是说从初始状态不一定能到达目标状态。因为排列有奇排列和偶排列两类,从奇排列不能转化成偶排列或相反。
如果一个数字0~8的随机排列871526340,用F(X)表示数字X前面比它小的数的个数,全部数字的F(X)之和为Y=∑(F(X)),如果Y为奇数则称原数字的排列是奇排列,如果Y为偶数则称原数字的排列是偶排列。
例如871526340这个排列的
Y=0+0+0+1+1+3+2+3+0=10
10是偶数,所以他偶排列。871625340
Y=0+0+0+1+1+2+2+3+0=9
9是奇数,所以他奇排列。
因此,可以在运行程序前检查初始状态和目标状态的窘是否相同,相同则问题可解,应当能搜索到路径。否则无解。



    本文转自阿凡卢博客园博客,原文链接:http://www.cnblogs.com/luxiaoxun/archive/2012/08/05/2624115.html,如需转载请自行联系原作者

八数码(广搜+剪枝) 所谓八数码,就是类似上图的这种游戏。规则是给定你起始状态和终止状态,问最少要几步能从起始状态变换到终止状态。对于这道题,算法思想不难想,就是广搜,把一个棋局看成一个状态,每次移动产生新的状态,一个个遍历直至找到目标状态。如果直接这样做会出现Memory limited exceeded 这样就遇到了第一个问题——剪枝,对于空格(也就是我们可移动的地方)可以有四个方向移动(上下左右),但是当我们把空格移到某个地方之后,那么在这个新的地方又有一种选择是再移回原来的地方,这样就会多出许多不必要的步骤。所以我们需. 阅读详情

相关推荐

A*算法解决八数码问题

以下内容仅是我个人对八数码问题A*寻路算法的理解,因为我是菜鸟一个,所以写的比较通俗。 八数码问题八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。

ssd_dai的博客 3万+

八数码问题 (头歌 educoder 启发式搜索)(python实现)

任务描述 本关任务:八数码问题是在一个3×3的棋盘上有1−8位数字随机分布,以及一个空格,空格相连的棋子可以滑动到空格中,问题的解是通过空格滑动,使得棋盘转化为目标状态,如下图所示。 为了简化问题的输入,首先将空格用数字0表示,然后将3×3的棋盘用9位长的字符串表示,则上图的初始状态为724506831,目标状态为012345678,本关卡所有目标状态均为012345678,也保证初始状态到目标状态有解。 测试输入: 724506831 这道题可以用很多搜索方法解决,启发式搜索为题目要求方法,但是因为

只想买把蝴蝶knife 8591

Python八数码问题深度、广度、A*算法解决

python简单编程八数码问题 实现结果:给定八数码的起始状态和目标状态,程序可以自动计算出所需要的步数,并能打印出每一步的变化。 使用深度搜索 import time as tm g_dict_layouts = {} #每个位置可交换的位置集合 g_dict_shifts = {0:[1, 3], 1:[0, 2, 4], 2:[1, 5], 3:[0,4,6...

开花的洋葱 2万+

人工智能实验——八数码难题

人工智能实验 八数码难题 利用了BFS算法DFS算法A*算法

weixin_51735061的博客 2万+

A*算法八数码问题 python解法

A*算法八数码问题 python解法 文章目录A*算法八数码问题 python解法问题描述A*算法八数码问题状态空间的定义各种操作的定义启发式函数的定义 人工智能课程中学习了A*算法,在耗费几小时完成了八数码问题和野人传教士问题之后,决定写此文章来记录一下,避免忘记 问题描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为12380

Weary_PJ的博客 3万+

基于Python的A*算法解决八数码问题

定义一个State类,主要用于表示搜索过程中的状态结点,包括结点的代价和状态信息,以及结点之间的关系。属性:gn:从起始结点到当前结点的实际代价。hn:从当前结点到目标结点的估计代价(启发式函数)。fn:综合代价,即gn+hn。child:子结点列表,存储从当前结点可以到达的所有子结点。par:父结点,指向生成当前结点的父结点。state:当前结点的状态矩阵。hash_value:当前结点状态矩阵的哈希值,用于在查找表中快速查找。方法:__lt__:小于运算符重载,用于结点比较。

qbx的博客 6265

A*算法八数码问题(numpy)

A*算法八数码问题(numpy)超详细

醉蕤的博客 6974

A*算法解决八数码问题C++实现

A*算法解决八数码问题C++实现。

小半杯的专栏 1万+

A*算法(解决八数码问题

A*算法简介 启发式搜索简介: 启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无谓的搜索路径,提高了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。 A*算法简介: A*算法就是对估价函数加上一些限制后得到的一种启发式搜索算法。 其搜索过程可能描述如下: 把初始节点S0放入Open表中,...

weixin_42032099的博客 2万+

A*算法求解八数码问题

实验内容 1 参考A算法核心代码,以8数码问题为例实现A算法的求解程序(编程语言不限),要求设计两种不同的估价函数。 2 在求解8数码问题的A算法程序中,设置相同的初始状态和目标状态,针对不同的估价函数,求得问题的解,并比较它们对搜索算法性能的影响,包括扩展节点数、生成节点数等。 3 对于8数码问题,设置上述2相同的初始状态和目标状态,用宽度优先搜索算法(即令估计代价h(n)=0的A算法)求得问...

Michelle 1万+

Python|A*算法解决八数码问题

一、Dijkstra算法A*算法比较 Dijkstra算法A*算法总结 Dijkstra算法计算源点到其他所有点的最短路径长度,A*关注点到点的最短路径(包括具体路径)。 Dijkstra算法建立在较为抽象的图论层面,A*算法可以更轻松地用在诸如游戏地图寻路中。 Dijkstra算法的实质是广度优先搜索,是一种发散式的搜索,所以空间复杂度和时间复杂度都比较高。对路径上的当前点,A*算法不但记录其到源点的代价,还计算当前点到目标点的期望代价,是一种启发式算法,也可以认为是一种深度优先的算法。 由第一

weixin_44217002的博客 1万+

启发式搜索算法:A算法(全局、局部择优算法)+A*算法 解决八数码问题

文章目录1.A 算法1.1.全局择优算法1.1.1.求解八数码1.2.局部择优算法2.A*算法2.1 解决八数码难题 参考博客:人工智能搜索策略:A*算法 1.A 算法 在图搜索算法中,如果能在搜索的每一步都利用估价函数f(n)=g(n)+h(n)对Open表中的节点进行排序,则该搜索算法为A算法。由于估价函数中带有问题自身的启发性信息,因此,A算法又称为启发式搜索算法。 对启发式搜索算法,又可根据搜索过程中选择扩展节点的范围,将其分为全局择优搜索算法和局部择优搜索算法。 ` 1.1.全局择优算法 在全

你说的白是什么白_ 2万+

【人工智能实验】A*算法求解8数码问题

1、画出A*算法求解N数码问题的流程图。根据A*算法的实验原理,;这样估价函数f(n)在g(n)一定的情况下,会或多或少的受距离估计值h(n)的制约,节点距目标点近,h值小,f值相对就小,能保证最短路的搜索向终点的方向进行,因此f是根据需要找到一条最小代价路径的观点来估算节点的。设计A*算法的流程图如图4.1.1所示,按照流程图编写伪代码,进而编写完整的程序。其中OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。

weixin_43442778的博客 12万+

八数码问题【人工智能实验】

八数码问题,也称九宫格问题,是经典的状态空间搜索问题。

m0_51336041的博客 1万+
上一篇: 我的友情链接
下一篇: JavaScript学习笔记(七)——函数的定义与调用
weixin_34007291
博客等级 码龄11年 4681粉丝 184原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值