实验二:隐式图的搜索问题
一、实验内容
编写九宫重排问题的启发式搜索(A*算法)求解程序。
在3х3组成的九宫棋盘上,放置数码为1~8的8个棋子,棋盘中留有一个空格,空格周围的棋子可以移动到空格中,从而改变棋盘的布局。根据给定初始布局和目标布局,编程给出一个最优的走法序列。输出每个状态的棋盘
二、A*算法的概念
A*算法,A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快。
三、编程语言以及开发环境的选择
这里采用c++编程语言以及vs2019开发环境。
四、实验思路
1、九宫重排思路
对于九宫重排问题的解决,首先要考虑是否有答案。每一个状态可认为是一个1×9的矩阵,问题即通过矩阵的变换,可以变换为目标状态对应的矩阵。由数学知识可知,可计算这两个有序数列的逆序值,如果两者都是偶数或奇数,则可通过变换到达,否则,这两个状态不可达。这样,就可以在具体解决问题之前判断出问题是否可解,从而可以避免不必要的搜索。
如果初始状态可以到达目标状态,那么采取什么样的方法呢?常用的状态空间搜索有深度优先和广度优先。广度和深度优先搜索有一个很大的缺陷就是他们都是在一个给定的状态空间中穷举。这在状态空间不大的情况下是很合适的算法,可是当状态空间十分大,且不预测的情况下就不可取了。他的效率实在太低,甚至不可完成。由于九宫重排问题状态空间共有9!个状态,如果选定了初始状态和目标状态,有9!/2个状态要搜索,考虑到时间和空间的限制,在这里要求采用A*算法求解。
A*算法是启发式搜索算法,启发式搜索就是在状态空间中对每一个搜索分支进行评估,得到最好的分支,再从这个分支进行搜索直到目标。这样可以省略大量无畏的搜索路径,提到了效率。在启发式搜索中,利用当前与问题有关的信息作为启发式信息指导搜索,这些信息能够有效省略大量无谓的搜索路径,大大提高了搜索效率。
启发式搜索算法定义了一个估价函数f(n),与问题相关的启发式信息都被计算为一定的 f(n) 的值,引入到搜索过程中。f(n) = g(n) +h(n)其中f(n) 是节点n的估价函数,g(n)是在状态空间中从初始节点到节点n的实际代价,h(n)是从节点n到目标节点最佳路径的估计代价。 在九宫重排问题中,显然g(n)就是从初始状态变换到当前状态所移动的步数,估计函数h(n)估计的是节点n到目标节点的距离,我们可以用欧几里德距离、曼哈顿距离或是两节的状态中数字的错位数来估计。
2、启发式策略工作过程演示
3、启发式搜索算法伪代码
(1) 存储结构的定义
//存储结构的定义
typedef struct node{//八数码结构体
int nine[N][N];//数码状态
int f;//估价值
int direct;//空格移动方向
struct node* parent;//父节点
}pNode;
(2) 启发式搜索算法的描述:
2.2算法伪代码
创建两个表,OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。算起点的估价值,将起点放入OPEN表。
while(OPEN!=NULL)
{
从OPEN表中取估价值f最小的节点n;
if(n节点==目标节点)
{break;}
for(当前节点n 的每个子节点X)
{
算X的估价值;
if(X inCLOSE){ //不添加
if(X in OPEN)
{
if( X的估价值小于OPEN表的估价值 )
{
把n设置为OPEN中与X相同值的父亲;
更新OPEN表中的估价值; //取最小路径的估价值
}// end if( X的估价值小于OPEN表的估价值 )
}//end if(X in OPEN)
else // if(X in OPEN)
{
把n设置为X的父亲;
并将X插入OPEN表中; //还没有排序
}//end else
}//end if(X inCLOSE)
}//end for
将n节点插入CLOSE表中;
按照估价值将OPEN表中的节点排序; //实际上是比较OPEN表内节点f的大小,从最小路径的节点向下进行。
}//end while(OPEN!=NULL)
保存路径,即 从终点开始,每个节点沿着父节点移动直至起点,这就是你的路径;
(3)类的定义
//-------------------------------------//类的声明
//用类封装
class Astar {
private:
Node* p;
Node* Goal; //目标
List Open; //Open表
List Close; //Clos表
public:
//构造函数
Astar(Node* goal){
Goal = copy(goal);
}
//Open表的添加
void openAdd(Node* temp);
//Open去头
Node* openDelete();
//Close的添加
void closeAdd();
// 用于判断是与Goal否相等
bool boolGoal() {
return Equal(p, Goal);
}
//扩展结点
bool Extend(Node* temp);
//0的移动
bool up(Node* temp);
bool down(Node* temp);
bool left(Node* temp);
bool right(Node* temp);
//判断两个结点是否相同
bool Equal(Node* s1, Node* s2);
//复制结点
Node* copy(Node* temp);
//
//g(s)的确定
int g(Node* temp);
//f(s)的确定
int f(Node* temp);
//h(s)的确定
int h(Node* temp);
//Open的排序
void openSort();
//判断Open表是否为空
bool openEmpty();
//判断是有Close相同
bool sameClose(Node* temp);
//判断是有Open相同
bool sameOpen(Node* temp);
//show
void show(Node* temp);
Node* GetP() {
return p;
}
/*void showall() {
int i = 0;
while (i < Open.length) {
show(Open.a[i]);
i++;
}
}*/
};
五、功能实现演示
测试数据:初始状态:123456780 目标状态:012345678
【输入格式】
输入包含三行,每行3各整数,分别为0-8这九个数字,以空格符号隔开,标识问题的初始状态。0表示空格,例如:
2 0 3
1 8 4
7 6 5