本文记录了,人工智能中简单的搜索策略中的路径搜索策略中的A*算法,来实现迷宫寻路的问题.(这只是一次本人的课外作业)
完整的程序源码已经发送到我的Git.这里只记录了我的思路和感想以及收获.
产生随机迷宫
迷宫求解没有迷宫怎么可以呢.而本人是个懒人,每次都要手动输入迷宫,重复性的工作让我很不爽.你可以在程序中用数组定义一个迷宫啊,有强迫症的我,怎么可以这样随便的要求自己的程序呢.及时求解算法的出来了,但是测试数据有限,还是让我很不爽的,所以,干脆先花一些时间,写个随机迷宫的产生吧.
遇事先搜索,看看前辈有没有经验分享,站在前辈的肩膀上,你可以少走很多弯路.
本人搜索到一篇论文:基于图的深度遍历产生随机迷宫的算法研究,看了以后,发现论文中有很多的错误,而且算法也不简洁,有很多地方完全可以非常简化的,但是非要写的那么复杂,生怕别人可以看懂似得.所以,就按照自己的理解,简单的实现了随机迷宫的产生.
参考上图,红色(起始位置),绿色(终点位置).而我产生随机迷宫的方法简单,产生的迷宫有一定的局限性.如图所示,从起始位置(最左上角)开始,每次随机的选取当前节点的右边或下边的节点作为扩展的节点.顺便还要判断一下下一个结点是否越界.通过这种方法会产生一条从矩形的一个点到另一个点的最短路径,该条路径是没有回路的路径,就这样,当随机选择出一条路径以后,其余的点任意摆布,是墙是路都可以,最终我们一定会得到一条最短的路径,我的简单随机迷宫诞生了.
至于更加完善的随机迷宫的产生,以后有时间了再学习一下.
A*算法遍历
如果在这里介绍这个算法,我感觉太没意思了.人工智能的学习还是有视频或者专人的讲授比较好,看书真的很枯燥的.这里我只说实现的过程中的困难和想法以及收获.
A*算法中重要的由2张表,open表和close表.
open表存放的都是没有扩展的节点,而且表里的内容(fn=gn+hn)是按照从小到大排序的。
close表存放的都是已经扩展过的节点,也就说已经遍历过的节点,这表里的节点中就存在着最终的路径。
A*算法先是利用open表和close表找到最终的目标节点,然后倒序从close表里依次把以前遍历过的路径节点找出来,即可得到完整的路径。
就想上图所示的简单迷宫一样,开始的时候,会同时扩展右边和下面的节点,存入open表里,然后选择open表中f最小的值,而在这个例子中,选择的是下面的方块,所以就这样沿着红色箭头的方向一直扩展下去,直到,发现open表中的2个节点有10和12,这时候,才调转方向,抛弃12,选择10((0,1)节点)继续开始新的遍历(蓝色箭头的方向),最终找到终点。
找到终点以后,图中的阴影的结点都在close表里面,然后,我们发现路径中的g从终点到起点是倒序的,h则是升序的。我们就利用这个特性来实现路径的输出。g=10的下一个节点的特征是(g=g-1&&节点是相邻的)根据这个判断,我们就会很容易的倒序找到完整的路径了。详细的实现的方法参见代码。
我的收获
花费了一周的时间来实现这个功能,使用的C++语言,而且中间也遇到了不少的困难。当你遇到的困难实在想不起好的解决方法的时候,那么不妨先放下,做其他的事情.晚上睡觉的时候,或者吃放走路洗澡的时候,脑子里想想就可以了.解决问题的关键是在一瞬间想起来的,实在不行的时候,不妨让大脑放松一下,不必心急火燎,容易上火.
bug(1)
堆栈的溢出:以前写程序的时候没有遇到过这种问题,曾经都使用过上万的数据空间,也没有出现过问题,但是这次的二维数组QT上面使用23*23,也就是迷宫的长和宽就出现这种情况了。长和宽23*23,那么顶点总共529个,转化为边的关系则有529*529=279841,额,确实有点大。解决方法很简单,定义成全局变量或者结构里面使用指针,函数里面使用动态的malloc内存分配,就OK了。
bug(2)
翻译过来就是算数异常,这个bug直到现在我也没有解决,因为不妨碍我程序的运行,这个bug只在程序调试的时候才出现。如下:
大家知道程序调试的环境和运行的环境是不一样的,就那Qt来说吧,运行的时候MinGW,而调试的时候是GDB,根据图示,这里应该出现了k=0的情况,random产生的随机数的范围是0~k-1所以,出错了。我是这么想的,也可能错了,如有见解,请指点。关于这次的程序,使我的GDB也崩溃了,害得我还重新安装了QT。
bug(3)
参考资料这么说的:出现了Segmentation fault,基本上的原因是,非法的内存访问。例如数组的越界,在循环操作时循环变量的控制问题,也有字符串拷贝时长度溢出,指针指向了非法的空间,还有就是申明一个指针,但却没有对其初始化,就直接引用,或者没有开辟内存空间就释放内存,所以要检查申请空间时间的成功。
编程习惯的改进:
说实话程序大了,代码多了,有时候就不在意良好的编程习惯了。这次在遇到bug的时候,请教了QQ群的网友,虽说最终的问题没有解决,但是,给我提了不少宝贵的建议。
(1)变量的使用和定义尽量额紧凑,靠在一起。
(2)最重要的也就是这一句:不要在判断语句中使用有副作用的语句。何为副作用?会改变程序状态的语句。如下:
if(getchar()=='Y'||getchar()=='y')
{}
getchar()从缓冲区读取一个字符,读一个少一个,那么读的过程中程序的状态是不是改变了?
好的写法如下:
char ch=toupper(getchar());
if('Y'==ch)
{}
学习的道路艰巨而漫长.