8 Puzzle/8 数码问题

                                                   

      这些天一直在研究8数码问题,用C++实现了A*。并且用了C++的模板使得它也能够处理15数码的问题。但是15数码的问题的难度超乎了我的想象。A*不管用了。一个更好方案是使用IDA*算法。前几天看了篇论文,发现了一个增强版的IDA*算法。所以决定使用这个威力加强版IDA*算法来处理15数码的问题。在这之前,我决定写一下处理8数码问题的一些心得。

 

 

 

 

 

 

 

 

     我的8数码问题的实现涉及到的一些东西。我简要地把它们列了一下:

 

1. 8数码问题的计算机表示

 使用二维数组来表示,具体下文会说到。

2. 曼哈顿距离 定义请看百度百科

 用来计算棋盘到棋盘的距离。棋盘1到棋盘2的距离就是棋盘1中每个数字的位置与棋盘2中对应数字的位置的曼哈顿距离的总和。

3. A*算法

 A*算法的大致思想是先根据初始棋盘随便走几步来生成一些棋盘,然后依据启发函数找到最接近最终棋盘的棋盘B,再从棋盘B出发生成些棋盘,用启发函数找到最接近最终棋盘的那个。以此类推直到找到的棋盘就是最终棋盘。                                                        

4.stl中的priority_queue和set

C++标准模板库中的两个容器,优先队列和集合。

5.C++函数对象

用来定制stl中的容器。

6.C++模板

使用模板的好处就是一个类可同时应用于8数码和15数码问题。

 

      我定义了模板类SlidingPuzzleNode<NP>来表示每个棋盘节点。每个棋盘应该具有如下的属性:

1. 棋盘的表示

我定义了一个二维数组来表示棋盘。上图中的3X3的棋盘可以这样定义char board[3][3]={

                                                                                                                                    {1,7,8},

                                                                                                                                    {6,5,2},

                                                                                                                                    {4,3,0}

                                                                                                                                 };

棋盘中的空格用0来表示。

2. 启发函数的计算

启发函数f由两部分构成。到最终棋盘的距离加上到初始棋盘的最短距离。到最终棋盘的距离使用曼哈顿距离,到初始棋盘的距离是从初始棋盘到当前棋盘走的最少步数。

3. 下一步走的棋盘节点

我定义了vector<SlidingPuzzleNode<NP>*> neighbors;来存放下步走的棋盘。

4.上一步棋盘节点

我定义了SlidingPuzzleNode* parent;来表示上一步的棋盘节点。

5. 区分每个棋盘的ID

下文中介绍。

6. 判断是否为目标棋盘

用棋盘的ID判断。

 

     A*算法本身不难理解。它有两个容器openset和closedset。openset存放待考察的棋盘,closedset存放考察过的棋盘。每次从openset中取出f()最小的棋盘,(f()是启发函数,它由g()和h()构成。g()是初始棋盘到当前棋盘走的最小步数,h()是当前棋盘到目标棋盘的启发式距离。h()的值越小,它离目标棋盘的距离越近。f()值最小表示它可能是初始棋盘到目标棋盘的最短路径中的某个节点。)如果它是最终棋盘则算法结束。否则放入closedset中。再得到它的下一步棋盘k,如果k已经在closedset中,表明这个棋盘k已经考察过了。如果k已在openset中出现过并且k的g()比openset中的g()小,更新openset中的g()值为k的g()的值。如果这些条件都不满足,直接放入openset中。wiki上的伪代码:

可以看到A*算法需要对openset和closedset这两个集合进行频繁地插入和查找操作。所以openset和closedset使用哪种数据结构来表示非常重要。启发函数的计算和取得下一步的棋盘等操作由棋盘节点SlidingPuzzleNode类自己来完成,在我自己实现的A*算法中就不需要操心了,这是面向对象的好处啊。下面的openset和closedset的表示:

     由于openset的作用只是每次取f()最小的棋盘,所以使用stl的优先队列priority_queue表示。priority_queue使用最大堆来实现,每次返回权值最大的元素。为此需要提供一个函数对象Greator把它变为最小堆。Greator很简单,源码如下:

priority_queue使用vector为底层容器,vector的一个很大特点就是当它已满时,会开辟一个更大的空间,把原来的元素通通复制过去。所以priority_queue存放的元素最好是复制代价很小的值类型或指针。所以openset的定义是这样子的:

      closedset由于要进行频繁地查找操作,我使用stl中的set表示。set使用红黑树来实现,每次查找的代价为O(lgn)。为了使用set,还必须提供<操作符。我使用函数对象Less,源码如下:

closedset的定义:

hash()函数返回每个棋盘的ID,两个不同的棋盘不可能返回相同的ID。为了达到这个目的,hash()函数的实现为取每个棋盘的数字序列。例如这个棋盘的数字序列为123804765。

有了这些就可以实现A*算法了:

  

完整的源代码下载

csdn下载

google下载

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值