引子:我接触8数码问题是在研一的时候上的《人工智能》课上,在某一章节介绍完深度优先搜索、广度优先搜索、贪婪搜索、A*搜索四种经典搜索策略以后,章节后面的一道习题便是让学生编程求解8数码问题。书上并未给出答案,我当时用不同的搜索策略分别实现了程序求解,但是普遍效率不高。这两天闲来无事,再把以前的程序翻出来看,决定重写一个。
程序界面:下图是程序运行的界面,程序从用户指定的状态开始搜索,直到找到目标状态。
在8数码问题中,只有空格(用数字0表示)可以移动,它每移动一步就是和相邻的一个数码交换一次位置。
上图左边的控制台上显示得的是(空格的)移动过程,经过20步以后达到目标状态,如上图右侧所示。顺便说下,鄙人的电脑是11年的ThinkPad E520,2.2GHz的i3处理器,32位win7系统。
代码细节:关于8数码问题的一个重要结论是[1]:两个不同的状态序列的逆序数,若奇偶性一致,则二状态可以互达,否则不能互达。示例:状态序列230148675的逆序数(0不考虑)是1+1+3+1+1=7.
算法C++源代码和编译过的(win7&winXp)exe文件均上传到了csdn资源,点此下载。
程序提供3中搜索模式可以在函数调用中确定:第一种是宽度优先搜索,搜索效率低(意味着需要更多的搜索时间和内存空间),但是保证找到全局最优解(步数最少);第二种是完全的启发式搜索,根据每个节点当前状态到目标状态的相似性来决定优先扩展哪个节点,该方法速度最快,但是一般找不到全局最优解;第三种方法是结合宽度优先的启发式搜索,即在启发函数中加入节点深度信息,这样的话再选择某个节点时需同时考虑其节点深度以及和目标的相似性,该方法搜索时间介于前两种方法之间,但是有很大概率找到全局最优解。上图显示的结果就是使用的第三中搜索策略得到的。
程序提供2种启发式函数:第一种是计算有多少个不在正确位置上的数码的个数来作为当前节点和目标节点的相似性度量;第二种是为每个错误放置的数码,计算其到正确位置的城市距离,由这些距离之和作为相似性度量。上图的示例程序就是采用第二种启发式。为了计算效率,我将启发式函数的选择用宏来定义,如果需要修改启发函数,则需要修改一个宏变量,并重新编译程序。
程序中保留了大多的灵活性:除了搜索模式和启发式函数可以修改以外,还可以定义不同的目标状态,如果需要以任意状态为目标,则需要:1)修改一个宏变量并重新编译程序(修改方法代码中有说明),使得代码允许其它的目标状态;2)在运行时,在main函数里设置新的目标状态。
需要说明的是:本文示例图中的目标状态在计算上是最快捷的,首先取数很方便,一般地查看目标在第i个位置上的值,则需要访问数组goal[i],而这里goal[i]==i,故而无需访问数组;第二,要想知道数码n的目标位置,则需要找到goal[i]==n,然后row=i/3,col=i%3. 但是这里的话,row=n/3,col=n%3. 当我们用8位无符号整型来表示各个数码值(0~8)时,n/3和n%3操作是非常快的,比访问数组还快。
程序还提供一种玩游戏模式,即用户自己通过W,S,A,D四个键分别控制空格往上、下、左、右四个方向移动一步,功能很简单,界面部分是用opencv做的。界面部分和搜索部分是完全分离的,demo文件中main函数部分显示了如何将它们组合使用。
参考文献:
[1] 用MFC动态编程实现8数码问题求解
版权声明:本文为博主原创文章,未经博主允许不得转载。