隐式图的搜索问题
一、问题概述及分析
1.问题概述
1)实验内容
编写九宫重排问题的启发式搜索(A*算法)求解程序。
在3х3组成的九宫棋盘上,放置数码为1~8的8个棋子,棋盘中留有一个空格,空格周围 的棋子可以移动到空格中,从而改变棋盘的布局。根据给定初始布局和目标布局,编程给出一个最优的走法序列。输出每个状态的棋盘
测试数据:初始状态:123456780 目标状态:012345678
2)实验要求
【输入格式】
输入包含三行,每行3各整数,分别为0-8这九个数字,以空格符号隔开,标识问题的初始状态。0表示空格,例如:
2 0 3
1 8 4
7 6 5
2.问题分析
对于九宫重排问题的解决,首先要考虑是否有答案。
每一个状态可认为是一个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到目标节点的距离,我们可以用欧几里德距离、曼哈顿距离或是两节的状态中数字的错位数来估计。
二、算法分析
1.A*算法
1.1 搜索区域(The Search Area)
我们假设某人要从 A 点移动到 B 点,但是这两点之间被一堵墙隔开。如图 1 ,绿色
是 A ,红色是 B ,中间蓝色是墙。
你应该注意到了,我们把要搜寻的区域划分成正方形的格子。这是寻路的第一步,简
化搜索区域,就像我们这里做的一样。这个特殊的方法把我们的搜索区域简化为了 2 维数组。数组的每一项代表一个格子,它的状态就是可走 (walkalbe) 和不可走(unwalkable) 。通过计算出从 A 到 B 需要走过哪些方格,就找到了路径。一旦路径找到了,人物便从一个方格的中心移动到另一个方格的中心,直至到达目的地。
方格的中心点我们成为“节点 (nodes) ”。如果你读过其他关于 A* 寻路算法的文章,
你会发现人们常常都在讨论节点。为什么不直接描述为方格呢?因为我们有可能把搜索区域划为为其他多变形而不是正方形,例如可以是六边形,矩形,甚至可以是任意多变形。而节点可以放在任意多边形里面,可以放在多变形的中心,也可以放在多边形的边上。我们使用这个系统,因为它最简单。
1.2 开始搜索(Starting the Search)
一旦我们把搜寻区域简化为一组可以量化的节点后,就像上面做的一样,我们下一步
要做的便是查找最短路径。在 A* 中,我们从起点开始,检查其相邻的方格,然后向四周扩展,直至找到目标。
我们这样开始我们的寻路旅途:
1) 从起点 A 开始,并把它就加入到一个由方格组成的 open list( 开放列表 ) 中。这个
open list 有点像是一个购物单。当然现在 open list 里只有一项,它就是起点 A ,后
面会慢慢加入更多的项。 Open list 里的格子是路径可能会是沿途经过的,也有可能
不经过。基本上 open list 是一个待检查的方格列表。
2) 查看与起点 A 相邻的方格 ( 忽略其中墙壁所占领的方格,河流所占领的方格及其他
非法地形占领的方格 ) ,把其中可走的 (walkable) 或可到达的 (reachable) 方格也加
入到 open list 中。把起点 A 设置为这些方格的父亲 (parent node 或 parent square) 。当我们在追踪路径时,这些父节点的内容是很重要的。稍后解释。
3) 把 A 从 open list 中移除,加入到 close list( 封闭列表 ) 中, close list 中的每个方
格都是现在不需要再关注的。
如下图所示,深绿色的方格为起点,它的外框是亮蓝色,表示该方格被加入到了close list 。与它相邻的黑色方格是需要被检查的,他们的外框是亮绿色。每个黑方格都有
一个灰色的指针指向他们的父节点,这里是起点 A 。
下一步,我们需要从 open list 中选一个与起点 A 相邻的方格,按下面描述的一样或
多或少的重复前面的步骤。但是到底选择哪个方格好呢?具有最小 F 值的那个。
1.3 路径排序(Path Sorting)
计算出组成路径的方格的关键是下面这个等式:F = G + H。
这里,**G = 从起点 A 移动到指定方格的移动代价,沿着到达该方格而生成的路径。
H = 从指定的方格移动到终点 B 的估算成本。**这个通常被称为试探法,有点让人混淆。为什么这么叫呢,因为这是个猜测。直到我们找到了路径我们才会知道真正的距离,因为途中有各种各样的东西 ( 比如墙壁,水等 ) 。本教程将教你一种计算 H 的方法,你也可以在网上找到其他方法。
我们的路径是这么产生的:反复遍历 open list ,选择 F 值最小的方格。这个过程稍
后详细描述。我们还是先看看怎么去计算上面的等式。
如上所述, G 是从起点A移动到指定方格的移动代价。在本例中,横向和纵向的移动代价为 10 ,对角线的移动代价为 14 。之所以使用这些数据,是因为实际的对角移动距离是 2 的平方根,或者是近似的 1.414 倍的横向或纵向移动代价。使用 10 和 14 就是为了简单起见。比例是对的,我们避免了开放和小数的计算。这并不是我们没有这个能力或是不喜欢数学。使用这些数字也可以使计算机更快。稍后你便会发现,如果不使用这些技巧,寻路算法将很慢。
既然我们是沿着到达指定方格的路径来计算 G 值,那么计算出该方格的 G 值的方法就是找出其父亲的 G 值,然后按父亲是直线方向还是斜线方向加上 10 或 14 。随着我们离开起点而得到更多的方格,这个方法会变得更加明朗。
有很多方法可以估算 H 值。这里我们使用 Manhattan 方法,计算从当前方格横向或纵向移动到达目标所经过的方格数,忽略对角移动,然后把总数乘以 10 。之所以叫做Manhattan 方法,是因为这很像统计从一个地点到另一个地点所穿过的街区数,而你不能斜向穿过街区。重要的是,计算 H 是,要忽略路径中的障碍物。这是对剩余距离的估算值,而不是实际值,因此才称为试探法。
把 G 和 H 相加便得到 F 。我们第一步的结果如下图所示。每个方格都标上了 F ,G , H 的值,就像起点右边的方格那样,左上角是 F ,左下角是 G ,右角是 H 。
好,现在让我们看看其中的一些方格。在标有字母的方格, G = 10 。这是因为水平方向从起点到那里只有一个方格的距离。与起点直接相邻的上方,下方,左方的方格的 G 值都是 10 ,对角线的方格 G 值都是 14 。H 值通过估算起点于终点 ( 红色方格 ) 的Manhattan 距离得到,仅作横向和纵向移动,并且忽略沿途的墙壁。使用这种方式,起点右边的方格到终点有 3 个方格的距离,因此 H = 30 。这个方格上方的方格到终点有 4 个方格的距离 ( 注意只计算横向和纵向距离 ) ,因此 H = 40 。对于其他的方格,你可以用同样的方法知道 H 值是如何得来的。每个方格的 F 值,再说一次,直接把 G 值和 H 值相加就可以了。
1.4 继续搜索(Continuing the Search)
为了继续搜索,我们从 open list 中选择 F 值最小的 ( 方格 ) 节点,然后对所选择的
方格作如下操作:
1) 把它从 open list 里取出,放到 close list 中。
2) 检查所有与它相邻的方格,忽略其中在 close list 中或是不可走 (unwalkable) 的方格( 比如墙,水,或是其他非法地形 ) ,如果方格不在 open lsit 中,则把它们加入到open list 中。
3) 把我们选定的方格设置为这些新加入的方格的父亲。
4) 如果某个相邻的方格已经在 open list 中,则检查这条路径是否更优,也就是说经由当前方格 ( 我们选中的方格 ) 到达那个方格是否具有更小的 G 值。如果没有,不做任何操作。相反,如果 G 值更小,则把那个方格的父亲设为当前方格 ( 我们选中的方格 ) ,然后重新计算那个方格的 F 值和 G 值。如果你还是很混淆,请参考下图。
Ok ,让我们看看它是怎么工作的。在我们最初的 9 个方格中,还有 8 个在 open list 中,起点被放入了 close list 中。在这些方格中,起点右边的格子的 F 值 40 最小,因此我们选择这个方格作为下一个要处理的方格。它的外框用蓝线打亮。
首先,我们把它从 open list 移到 close list 中 ( 这就是为什么用蓝线打亮的原因
了 ) 。然后我们检查与它相邻的方格。它右边的方格是墙壁,我们忽略。它左边的方格是起点,在 close list 中,我们也忽略。其他 4 个相邻的方格均在 open list 中,我们需要检查经由这个方格到达那里的路径是否更好,使用 G 值来判定。让我们看看上面的方格。它现在的 G 值为 14 。如果我们经由当前方格到达那里, G 值将会为 20(其中 10 为到达当前方格的 G 值,此外还要加上从当前方格纵向移动到上面方格的 G 值 10) 。显然20 比 14 大,因此这不是最优的路径。如果你看图你就会明白。直接从起点沿对角线移动到那个方格比先横向移动再纵向移动要好。
当把 4 个已经在 open list 中的相邻方格都检查后,没有发现经由当前方格的更好路径,因此我们不做任何改变。现在我们已经检查了当前方格的所有相邻的方格,并也对他们作了处理,是时候选择下一个待处理的方格了。因此再次遍历我们的 open list ,现在它只有 7 个方格了,我们需要选择 F 值最小的那个。有趣的是,这次有两个方格的 F 值都 54 ,选哪个呢?没什么关系。从速度上考虑,选择最后加入 open list 的方格更快。这导致了在寻路过程中,当靠近目标时,优先使用新找到的方格的偏好。但是这并不重要。 ( 对相同数据的不同对待,导致两中版本的 A* 找到等长的不同路径 ) 。
我们选择起点右下方的方格,如下图所示。
这次,我们检查相邻的方格时,发现它右边的方格是墙,忽略之。上面的也一样。
把墙下面的一格也忽略掉。为什么?因为如果不穿越墙角的话,你不能直接从当前方
格移动到那个方格。你需要先往下走,然后再移动到那个方格,这样来绕过墙角。 ( 注
意:穿越墙角的规则是可选的,依赖于你的节点是怎么放置的 )
这样还剩下 5 个相邻的方格。当前方格下面的 2 个方格还没有加入 open list ,所以
把它们加入,同时把当前方格设为他们的父亲。在剩下的 3 个方格中,有 2 个已经在
close list 中 ( 一个是起点,一个是当前方格上面的方格,外框被加亮的 ) ,我们忽略它
们。最后一个方格,也就是当前方格左边的方格,我们检查经由当前方格到达那里是否具有更小的 G 值。没有。因此我们准备从 open list 中选择下一个待处理的方格。
不断重复这个过程,直到把终点也加入到了 open list 中,此时如下图所示。
注意,在起点下面 2 格的方格的父亲已经与前面不同了。之前它的 G 值是 28 并且
指向它右上方的方格。现在它的 G 值为 20 ,并且指向它正上方的方格。这在寻路过程
中的某处发生,使用新路径时 G 值经过检查并且变得更低,因此父节点被重新设置, G
和 F 值被重新计算。尽管这一变化在本例中并不重要,但是在很多场合中,这种变化会导致寻路结果的巨大变化。
那么我们怎么样去确定实际路径呢?很简单,从终点开始,按着箭头向父节点移动,
这样你就被带回到了起点,这就是你的路径。如下图所示。从起点 A 移动到终点 B 就是
简单从路径上的一个方格的中心移动到另一个方格的中心,直至目标。就是这么简单!
1.5 A算法总结(Summary of the A Method)
Ok ,现在你已经看完了整个的介绍,现在我们把所有步骤放在一起:
1. 把起点加入 open list 。
2. 重复如下过程:
a. 遍历 open list ,查找 F 值最小的节点,把它作为当前要处理的节点。
b. 把这个节点移到 close list 。
c. 对当前方格的 8 个相邻方格的每一个方格?
◆ 如果它是不可抵达的或者它在 close list 中,忽略它。否则,做如下操作。
◆ 如果它不在 open list 中,把它加入 open list ,并且把当前方格设置为它的
父亲,记录该方格的 F , G 和 H 值。
◆ 如果它已经在 open list 中,检查这条路径 ( 即经由当前方格到达它那里 )
是否更好,用 G 值作参考。更小的 G 值表示这是更好的路径。如果是这
样,把它的父亲设置为当前方格,并重新计算它的 G 和 F 值。如果你的
open list 是按 F 值排序的话,改变后你可能需要重新排序。
d. 停止,当你
◆ 把终点加入到了 open list 中,此时路径已经找到了,或者
◆ 查找终点失败,并且 open list 是空的,此时没有路径。
3. 保存路径。从终点开始,每个方格沿着父节点移动直至起点,这就是你的路径。
2.启发式搜索算法的描述
(1)把初始节点S0 放入Open表中,f(S0)=g(S0)+h(S0);
(2)如果Open表为空,则问题无解,失败退出;
(3)把Open表的第一个节点取出放入Closed表,并记该节点为n;
(4)考察节点n是否为目标节点。若是,则找到了问题的解,成功退出;
(5)若节点n不可扩展,则转到第(2)步;
(6)扩展节点n,生成子节点ni(i=1,2,……),计算每一个子节点的估价值f(ni) (i=1,2,……),并为每一个子节点设置指向父节点的指针,然后将这些子节点放入Open表中;
(7)根据各节点的估价函数值,对Open表中的全部节点按从小到大的顺序重新进行排序;
(8)转到第(2)步。
启发式搜索算法的工作过程:
三、算法设计
1.定义8位数码结构体
#pragma once
#define OK true
#define ERROR false
#define MAXLISTSIZE 10000 /*最大表长*/
#define MAXSTEPSIZE 100
int GoalStatus[9]; /*目标状态*/
typedef struct EightDigital
{
int InitalStatus[9]; /*存储0~8九位数字*/
int G_ActualCost; /*G_ActualCost是初始结点到结点n的实际代价*/
int H_EstimatedCost; /*H_EstimatedCost代表从结点n到目标结点的估计代价*/
int F_ValuationFunction; /*F_ValuationFunction存储估价函数值*/
int Zero_Position; /*存储0的位置*/
int Storage_Operator; /*实现上下左右移动操作符*/
EightDigital *Father; /*父结点*/
};
2.具体算法实现
2.1 统计初始状态不在位的格子数
int Count(int * InitalStatus)
{
int count=0; /*计数元素*/
int i;
for (i = 0; i <= 8; i++)
{
if (GoalStatus[i] != InitalStatus[i]) /*与目标状态相比较,初始状态不在位的格子*/
{
count++;
}
}
return count;
}
2.2 判断新结点是否存在在两个表中
/*判断新结点是否已经出现在openlist表或closelist中*/
int IsExist(EightDigital *node)
{
int i, j;
/*计算不在位的字格数,如果为0,则证明给函数的节点在表中已存在*/
int count = 0;
int status[9];
Node = new EightDigital;
Node = node;
for (i = 0; i <= 8; i++)
{
status[i] = Node->InitalStatus[i];
}
/*判断是否在openlist表*/
for (i = 0; i < open - 1; i++)
{
for (j = 0; j<= 8; j++)
{
if (status[j] != OPEN_LIST[i].InitalStatus[j])
{
count++;
}
}
if (count == 0)
{
/*OPEN表中,返回i(节点在OPEN的位置)+ 1(在OPEN找到该节点)*/
return i + 1;
}
count = 0; /*重置*/
}
/*判断是否在closelist表*/
for (i = 0; i <= close - 1; i++)
{
for (j = 0; j <= 8; j++)
{
if (status[j] != CLOSE_LIST[i].InitalStatus[j])
{
count++;
}
}
if (count == 0)
{
/*在closelist表中,返回-i(i为节点在CLOSE的位置)- 1(在CLOSE找到该节点)*/
return (-i) - 1;
}
count = 0;
}
return OK;
}
2.3 初始化结点
EightDigital* EightDigital_Init(int status[10], int zero_position, int g_actualCost, EightDigital*father, int storage_operator)
{
int i;
Node = new EightDigital;
for (i = 0; i <= 8; i++)
{
Node->InitalStatus[i] = status[i];
}
Node->Zero_Position = zero_position;
Node->G_ActualCost = g_actualCost;
Node->H_EstimatedCost = Count(Node->InitalStatus);
Node->F_ValuationFunction = Node->G_ActualCost + Node->H_EstimatedCost;
Node->Father = father;
Node->Storage_Operator = storage_operator;
return Node;
}
2.4 实现移动
/*1.左移*/
int *Shift_Left(int *status, int s)
{
int temp, i;
static int move_status[9];
for (int i = 0; i <= 8; i++)
{
move_status[i] = status[i];
}
/*左移则是下标减1,需要与前一个位置进行值的交换*/
temp = move_status[s- 1];
move_status[s - 1] = 0;
move_status[s] = temp;
return move_status;
}
/*2.右移*/
int *Shift_Right(int *status, int s)
{
int temp, i;
static int move_status[9];
for (i = 0; i <= 8; i++)
{
move_status[i] = status[i];
}
/*右移则是下标加1,需要与下一位置进行值的交换*/
temp = move_status[s + 1];
move_status[s + 1] = 0;
move_status[s] = temp;
return move_status;
}
/*3.上移*/
int *Shift_Up(int *status, int s)
{
int temp, i;
static int move_status[9];
for (i = 0; i <= 8; i++)
{
move_status[i] = status[i];
}
/*上移则是下标减3,需要与前三个位置进行值的交换*/
temp = move_status[s - 3];
move_status[s - 3] = 0;
move_status[s] = temp;
return move_status;
}
/*4.下移*/
int *Shift_Down(int *status, int s)
{
int temp, i;
static int move_status[9];
for (i = 0; i <= 8; i++)
{
move_status[i] = status[i];
}
/*下移则是下标加3,需要与后三个位置进行值的交换*/
temp = move_status[s + 3];
move_status[s + 3] = 0;
move_status[s] = temp;
return move_status;
}
2.5 判断子节点
void IsExitAndOper(EightDigital *N)
{
int i;
int inList;
Node = new EightDigital;
Node = N;
/*首结点的下一个结点*/
if (Node->G_ActualCost == 1)
{
OPEN_LIST[open] = *Node;
open++;
return;
}
/*判断新节点是否在OPEN或CLOSE中*/
inList = IsExist(Node);
/*如果均不在两个表中*/
if (inList == 0)
{
OPEN_LIST[open] = *Node;
open++;
}
/*如果在OPEN中,说明从初始节点到该节点找到了不同路径,保留耗散值短的那条路径*/
else if (inList > 0)
{
/*如果openlist表内节点估计函数值大于新节点估计函数值,用新节点代替表内节点*/
if (OPEN_LIST[inList - 1].F_ValuationFunction > Node->F_ValuationFunction)
{
OPEN_LIST[inList - 1] = *Node;
}
}
/*如果在CLOSE中,说明初始节点到该节点有两条路径,如果新找到的路径耗散值大,
什么都不做,如果较小,将其从CLOSE中取出放入OPEN中*/
else if (inList < 0)
{
inList = -inList;
/*如果较小*/
if (CLOSE_LIST[close - 1].F_ValuationFunction > Node->F_ValuationFunction)
{
OPEN_LIST[open] = *Node;
open++;
}
for (int i = inList - 1; i <= close + 1; i++)
{
CLOSE_LIST[i] = CLOSE_LIST[i + 1];
}
close--;
}
}
2.6 寻找最佳路径函数
EightDigital *Search_Best_Path()
{
int *status;
int i, j;
EightDigital *Temp;
while (OK)
{
Temp= new EightDigital;
/*对openlist表中结点按照F排序*/
for (i = open - 1; i > 0; i--)
{
for (j = 0; j < i; j++)
{
/*从小到大排序*/
if (OPEN_LIST[j].F_ValuationFunction > OPEN_LIST[j + 1].F_ValuationFunction)
{
*Temp = OPEN_LIST[j + 1];
OPEN_LIST[j + 1] = OPEN_LIST[j];
OPEN_LIST[j] = *Temp;
}
}
}
Node = new EightDigital;
/*从open list取出第一个元素,其F值最小*/
*Node = OPEN_LIST[0];
/*判断该节点是否是目标节点,若是,则不在位的格数为0,算法结束,若不是,则将该结点进行扩展*/
if (!Count(Node->InitalStatus))
{
break;
}
Temp = Node;
/*将子节点的子节点放入closelist中*/
CLOSE_LIST[close] = *Node;
close++;
/*将子节点的子节点从openlist中释放*/
for (i = 0; i <=open - 1; i++)
{
OPEN_LIST[i] = OPEN_LIST[i + 1];
}
open--;
/*若可以左移,则进行左移创造新结点,下标为0,3,6则不能进行左移*/
if ((Temp->Zero_Position) % 3 >= 1)
{
Node = new EightDigital;
status = Shift_Left(Temp->InitalStatus, Temp->Zero_Position);
/*初始化结点*/
Node = EightDigital_Init(status, Temp->Zero_Position - 1, Temp->G_ActualCost + 1, Temp, 1);
/*判断新结点是否在openlist或者closelist表中并进行相关操作*/
IsExitAndOper(Node);
}
/*若可以右移,则进行右移创造新结点,下标为2,5,8则不能进行右移*/
if ((Temp->Zero_Position) % 3 <= 1)
{
Node = new EightDigital;
status = Shift_Right(Temp->InitalStatus, Temp->Zero_Position);
/*初始化结点*/
Node = EightDigital_Init(status, Temp->Zero_Position +1, Temp->G_ActualCost + 1, Temp, 2);
/*判断新结点是否在openlist或者closelist表中并进行相关操作*/
IsExitAndOper(Node);
}
/*若可以上移,则进行上移创造新结点,下标为0,1,2则不能进行上移*/
if ((Temp->Zero_Position) >= 3)
{
Node = new EightDigital;
status = Shift_Up(Temp->InitalStatus, Temp->Zero_Position);
/*初始化结点*/
Node = EightDigital_Init(status, Temp->Zero_Position -3, Temp->G_ActualCost + 1, Temp, 3);
/*判断新结点是否在openlist或者closelist表中并进行相关操作*/
IsExitAndOper(Node);
}
/*若可以下移,则进行下移创造新结点 ,下标为6,7,8则不可以*/
if (Temp->Zero_Position <= 5)
{
Node = new EightDigital;
status = Shift_Down(Temp->InitalStatus, Temp->Zero_Position);
/*初始化结点*/
Node = EightDigital_Init(status, Temp->Zero_Position + 3, Temp->G_ActualCost + 1, Temp, 4);
/*判断新结点是否在openlist或者closelist表中并进行相关操作*/
IsExitAndOper(Node);
}
//如果open=0, 证明算法失败, 没有解
if (open == 0)
{
return nullptr;
}
}
}
2.7 输出
void ShowStep(EightDigital *Node)
{
int STEP[MAXSTEPSIZE];
int STATUS[MAXSTEPSIZE][9];
int step = 0;
int i, j;
int Totalstep = Node->G_ActualCost;
while (Node)
{
STEP[step] = Node->Zero_Position;
for (i = 0; i <= 8; i++)
{
STATUS[step][i] = Node->InitalStatus[i];
}
step++;
Node = Node->Father;
}
cout << " " << endl;
cout << "****总步数为****:" << Totalstep << endl;
cout << " " << endl;
for (i = step - 1; i >= 0; i--)
{
if (STEP[i] == 1)
{
cout << "应向左移动一步" << endl;
}
else if (STEP[i] == 2)
{
cout << "应向右移动一步" << endl;
}
else if (STEP[i] == 3)
{
cout << "应向上移动一步" << endl;
}
else if (STEP[i] == 4)
{
cout << "应向下移动一步" << endl;
}
else if (STEP[i] == 0)
{
cout << "已启动:" << endl;
}
for (j = 0; j <= 8; j++)
{
cout << STATUS[i][j] << " ";
if (j == 2 || j == 5 || j == 8)
{
cout << endl;
}
}
cout << " " << endl;
}
}
四、实验程序
1. 头文件(.h文件)
1.1 Modular design.h
#pragma once
#define OK true
#define ERROR false
#define MAXLISTSIZE 10000 /*最大表长*/
#define MAXSTEPSIZE 100
int GoalStatus[9]; /*目标状态*/
typedef struct EightDigital
{
int InitalStatus[9]; /*存储0~8九位数字*/
int G_ActualCost; /*G_ActualCost是初始结点到结点n的实际代价*/
int H_EstimatedCost; /*H_EstimatedCost代表从结点n到目标结点的估计代价*/
int F_ValuationFunction; /*F_ValuationFunction存储估价函数值*/
int Zero_Position; /*存储0的位置*/
int Storage_Operator; /*实现上下左右移动操作符*/
EightDigital *Father; /*父结点*/
};
1.2 Astar algorithm.h
#pragma once
#include "Modular design.h"
#include<iostream>
using namespace std;
EightDigital OPEN_LIST[MAXLISTSIZE]; /*openlist表*/
EightDigital CLOSE_LIST[MAXLISTSIZE]; /*closelist表*/
int open = 0, close = 0;
EightDigital* Node; /*定义结点*/
/*与目标状态相比较,初始状态不在位的格子数*/
int Count(int * InitalStatus)
{
int count=0; /*计数元素*/
int i;
for (i = 0; i <= 8; i++)
{
if (GoalStatus[i] != InitalStatus[i]) /*与目标状态相比较,初始状态不在位的格子*/
{
count++;
}
}
return count;
}
/*判断新结点是否已经出现在openlist表或closelist中*/
int IsExist(EightDigital *node)
{
int i, j;
/*计算不在位的字格数,如果为0,则证明给函数的节点在表中已存在*/
int count = 0;
int status[9];
Node = new EightDigital;
Node = node;
for (i = 0; i <= 8; i++)
{
status[i] = Node->InitalStatus[i];
}
/*判断是否在openlist表*/
for (i = 0; i < open - 1; i++)
{
for (j = 0; j<= 8; j++)
{
if (status[j] != OPEN_LIST[i].InitalStatus[j])
{
count++;
}
}
if (count == 0)
{
/*OPEN表中,返回i(节点在OPEN的位置)+ 1(在OPEN找到该节点)*/
return i + 1;
}
count = 0; /*重置*/
}
/*判断是否在closelist表*/
for (i = 0; i <= close - 1; i++)
{
for (j = 0; j <= 8; j++)
{
if (status[j] != CLOSE_LIST[i].InitalStatus[j])
{
count++;
}
}
if (count == 0)
{
/*在closelist表中,返回-i(i为节点在CLOSE的位置)- 1(在CLOSE找到该节点)*/
return (-i) - 1;
}
count = 0;
}
return OK;
}
/*初始化结点*/
EightDigital* EightDigital_Init(int status[10], int zero_position, int g_actualCost, EightDigital*father, int storage_operator)
{
int i;
Node = new EightDigital;
for (i = 0; i <= 8; i++)
{
Node->InitalStatus[i] = status[i];
}
Node->Zero_Position = zero_position;
Node->G_ActualCost = g_actualCost;
Node->H_EstimatedCost = Count(Node->InitalStatus);
Node->F_ValuationFunction = Node->G_ActualCost + Node->H_EstimatedCost;
Node->Father = father;
Node->Storage_Operator = storage_operator;
return Node;
}
/*实现上下左右移动*/
/*1.左移*/
int *Shift_Left(int *status, int s)
{
int temp, i;
static int move_status[9];
for (int i = 0; i <= 8; i++)
{
move_status[i] = status[i];
}
/*左移则是下标减1,需要与前一个位置进行值的交换*/
temp = move_status[s- 1];
move_status[s - 1] = 0;
move_status[s] = temp;
return move_status;
}
/*2.右移*/
int *Shift_Right(int *status, int s)
{
int temp, i;
static int move_status[9];
for (i = 0; i <= 8; i++)
{
move_status[i] = status[i];
}
/*右移则是下标加1,需要与下一位置进行值的交换*/
temp = move_status[s + 1];
move_status[s + 1] = 0;
move_status[s] = temp;
return move_status;
}
/*3.上移*/
int *Shift_Up(int *status, int s)
{
int temp, i;
static int move_status[9];
for (i = 0; i <= 8; i++)
{
move_status[i] = status[i];
}
/*上移则是下标减3,需要与前三个位置进行值的交换*/
temp = move_status[s - 3];
move_status[s - 3] = 0;
move_status[s] = temp;
return move_status;
}
/*4.下移*/
int *Shift_Down(int *status, int s)
{
int temp, i;
static int move_status[9];
for (i = 0; i <= 8; i++)
{
move_status[i] = status[i];
}
/*下移则是下标加3,需要与后三个位置进行值的交换*/
temp = move_status[s + 3];
move_status[s + 3] = 0;
move_status[s] = temp;
return move_status;
}
/*判断子节点是否在closelist或openlist中*/
/*定义表示新生成节点是否在OPEN表或CLOSE表中, 值为0 均不在,值>0 只在OPEN表,值<0 只在CLOSE表*/
void IsExitAndOper(EightDigital *N)
{
int i;
int inList;
Node = new EightDigital;
Node = N;
/*首结点的下一个结点*/
if (Node->G_ActualCost == 1)
{
OPEN_LIST[open] = *Node;
open++;
return;
}
/*判断新节点是否在OPEN或CLOSE中*/
inList = IsExist(Node);
/*如果均不在两个表中*/
if (inList == 0)
{
OPEN_LIST[open] = *Node;
open++;
}
/*如果在OPEN中,说明从初始节点到该节点找到了不同路径,保留耗散值短的那条路径*/
else if (inList > 0)
{
/*如果openlist表内节点估计函数值大于新节点估计函数值,用新节点代替表内节点*/
if (OPEN_LIST[inList - 1].F_ValuationFunction > Node->F_ValuationFunction)
{
OPEN_LIST[inList - 1] = *Node;
}
}
/*如果在CLOSE中,说明初始节点到该节点有两条路径,如果新找到的路径耗散值大,
什么都不做,如果较小,将其从CLOSE中取出放入OPEN中*/
else if (inList < 0)
{
inList = -inList;
/*如果较小*/
if (CLOSE_LIST[close - 1].F_ValuationFunction > Node->F_ValuationFunction)
{
OPEN_LIST[open] = *Node;
open++;
}
for (int i = inList - 1; i <= close + 1; i++)
{
CLOSE_LIST[i] = CLOSE_LIST[i + 1];
}
close--;
}
}
/*寻找最佳路径函数*/
EightDigital *Search_Best_Path()
{
int *status;
int i, j;
EightDigital *Temp;
while (OK)
{
Temp= new EightDigital;
/*对openlist表中结点按照F排序*/
for (i = open - 1; i > 0; i--)
{
for (j = 0; j < i; j++)
{
/*从小到大排序*/
if (OPEN_LIST[j].F_ValuationFunction > OPEN_LIST[j + 1].F_ValuationFunction)
{
*Temp = OPEN_LIST[j + 1];
OPEN_LIST[j + 1] = OPEN_LIST[j];
OPEN_LIST[j] = *Temp;
}
}
}
Node = new EightDigital;
/*从open list取出第一个元素,其F值最小*/
*Node = OPEN_LIST[0];
/*判断该节点是否是目标节点,若是,则不在位的格数为0,算法结束,若不是,则将该结点进行扩展*/
if (!Count(Node->InitalStatus))
{
break;
}
Temp = Node;
/*将子节点的子节点放入closelist中*/
CLOSE_LIST[close] = *Node;
close++;
/*将子节点的子节点从openlist中释放*/
for (i = 0; i <=open - 1; i++)
{
OPEN_LIST[i] = OPEN_LIST[i + 1];
}
open--;
/*若可以左移,则进行左移创造新结点,下标为0,3,6则不能进行左移*/
if ((Temp->Zero_Position) % 3 >= 1)
{
Node = new EightDigital;
status = Shift_Left(Temp->InitalStatus, Temp->Zero_Position);
/*初始化结点*/
Node = EightDigital_Init(status, Temp->Zero_Position - 1, Temp->G_ActualCost + 1, Temp, 1);
/*判断新结点是否在openlist或者closelist表中并进行相关操作*/
IsExitAndOper(Node);
}
/*若可以右移,则进行右移创造新结点,下标为2,5,8则不能进行右移*/
if ((Temp->Zero_Position) % 3 <= 1)
{
Node = new EightDigital;
status = Shift_Right(Temp->InitalStatus, Temp->Zero_Position);
/*初始化结点*/
Node = EightDigital_Init(status, Temp->Zero_Position +1, Temp->G_ActualCost + 1, Temp, 2);
/*判断新结点是否在openlist或者closelist表中并进行相关操作*/
IsExitAndOper(Node);
}
/*若可以上移,则进行上移创造新结点,下标为0,1,2则不能进行上移*/
if ((Temp->Zero_Position) >= 3)
{
Node = new EightDigital;
status = Shift_Up(Temp->InitalStatus, Temp->Zero_Position);
/*初始化结点*/
Node = EightDigital_Init(status, Temp->Zero_Position -3, Temp->G_ActualCost + 1, Temp, 3);
/*判断新结点是否在openlist或者closelist表中并进行相关操作*/
IsExitAndOper(Node);
}
/*若可以下移,则进行下移创造新结点 ,下标为6,7,8则不可以*/
if (Temp->Zero_Position <= 5)
{
Node = new EightDigital;
status = Shift_Down(Temp->InitalStatus, Temp->Zero_Position);
/*初始化结点*/
Node = EightDigital_Init(status, Temp->Zero_Position + 3, Temp->G_ActualCost + 1, Temp, 4);
/*判断新结点是否在openlist或者closelist表中并进行相关操作*/
IsExitAndOper(Node);
}
//如果open=0, 证明算法失败, 没有解
if (open == 0)
{
return nullptr;
}
}
}
/*展现*/
void ShowStep(EightDigital *Node)
{
int STEP[MAXSTEPSIZE];
int STATUS[MAXSTEPSIZE][9];
int step = 0;
int i, j;
int Totalstep = Node->G_ActualCost;
while (Node)
{
STEP[step] = Node->Zero_Position;
for (i = 0; i <= 8; i++)
{
STATUS[step][i] = Node->InitalStatus[i];
}
step++;
Node = Node->Father;
}
cout << " " << endl;
cout << "****总步数为****:" << Totalstep << endl;
cout << " " << endl;
for (i = step - 1; i >= 0; i--)
{
if (STEP[i] == 1)
{
cout << "应向左移动一步" << endl;
}
else if (STEP[i] == 2)
{
cout << "应向右移动一步" << endl;
}
else if (STEP[i] == 3)
{
cout << "应向上移动一步" << endl;
}
else if (STEP[i] == 4)
{
cout << "应向下移动一步" << endl;
}
else if (STEP[i] == 0)
{
cout << "已启动:" << endl;
}
for (j = 0; j <= 8; j++)
{
cout << STATUS[i][j] << " ";
if (j == 2 || j == 5 || j == 8)
{
cout << endl;
}
}
cout << " " << endl;
}
}
2. 源文件(.cpp文件)
2.1 main.cpp
#include"Astar algorithm.h"
int main()
{
int InitalStatus[9];
int GoalStatus[9];
int i, beginTime, endTime;
EightDigital *InitalNode;
EightDigital *GoalNode;
cout << "请输入初始状态:" << endl;
for (i = 0; i <= 8; i++)
{
cin >> InitalStatus[i];
}
cout << endl;
cout << "请输入目标状态:" << endl;
for (i = 0; i <= 8; i++)
{
cin >> GoalStatus[i];
}
beginTime = clock();
/*判断0的位置*/
for (i = 0; i <= 8; i++)
{
if (InitalStatus[i] == 0)
break;
}
/*获得初始节点*/
InitalNode= EightDigital_Init(InitalStatus, i, 0, NULL, 0);
/*将初始节点放入open中*/
OPEN_LIST[open] = *InitalNode;
open++;
/*寻找最佳路径*/
GoalNode = Search_Best_Path();
if (!GoalNode)
{
cout << "抱歉,该目标无法实现!" << endl;
}
else
{
ShowStep(GoalNode);
}
endTime = clock();
cout << "本次运行:" << endTime - beginTime << "ms" << endl;
return 0;
}