赞
踩
八数码游戏包括一个33的棋盘,棋盘上摆放着8个数字的棋子,留下一个空位。与空位相邻的棋子可以滑动到空位中。游戏的目的是要达到一个特定的目标状态。标注的形式化如下: 初始状态 7 5 3
1 6 4
2 8 0
目标状态 1 2 3
8 0 4
7 6 5
状态: 状态描述了8个棋子和空位在棋盘的9个方格上的分布。
初始状态: 任何状态都可以被指定为初始状态。
操作符: 用来产生4个行动(上下左右移动)。
目标测试: 用来检测状态是否能匹配上图的目标布局。
路径费用函数:每一步的费用为1,因此整个路径的费用是路径中的步数。
现在任意给定一个初始状态,要求找到一种搜索策略,用尽可能少的步数得到上图的目标状态
启发式搜索算法 A,一般简称 A算法 是一种典型的启发式搜索算法,其基本思想:
定义一个评估函数f,对当前的搜索状态进行评估,找到一个最有希望的节点进行扩展。
评估函数的形式如下:
f(n)=g(n)+h(n)
其中:
n是被评估的节点
f(n) 是节点n从初始点到目标点的估价函数。
g(n) 是在状态空间中从初始节点到n节点的实际代价。
h(n)是从n到目标节点最佳路径的估计代价。
f(n)=g(n)+h(n) 表示从初始节点s经过节点n到目标节点g的评估代价
保证找到最短路径(最优解)的条件,关键在于估价函数h(n)的选取。估价值h(n)<= n到目标节点的距离实际值,这种情况下,搜索的点数多,搜索范围大,效率低。但能得到最优解。如果估价值>实际值, 搜索的点数少,搜索范围小,效率高,但不能保证得到最优解。
搜索中利用启发式信息,对当前未扩展结点根据设定的估价函数值选取离目标最近的结点进行扩展,从而缩小搜索空间,更快的得到最优解,提高效率。
1.3.1.1 A算法应用
在启发式搜索算法中,根据估价函数值,按由小到大的次序对Open表中的节点进行重新排序,这就是有序搜索法。因此,此时的Open表是一个按节点的启发估价函数值的大小为序排列的一个优先队。
有序搜索算法如下:
1、将初始节点S0放入Open表中;
2、如Open表为空,则搜索失败,退出;
3、把Open表的第一个节点取出,放入到Closed表中,并把该节点记为节点n;
4、如果节点n是目标节点,则搜索成功,求得一个解,退出;
5、扩展节点n,生成一组子节点,对既不在Open表中也不在Closed表中的子节点,计算出相应的估价函数值;
6、把节点n的子节点放到Open表中;
7、对Open表中的各节点按估价函数值从小到大排列;
8、转到2 。
在A*算法中,启发性信息用一个特别的估价函数f*来表示:
f*(n)=g*(n)+h*(n)
式中:
g*(n)为从初始节点到节点n的最佳路径所付出的代价;
h*(n)是从n到目标节点的最佳路径所付出的代价;
f*(n)是从初始节点出发通过节点n到达目标节点的最佳路径的总代价。
(在这里要特别说明前面所说的f(n)=g(n)+h(n) 未带 * 为评估代价而非一定是最优代价)
基于上述g*(n)和h*(n)的定义,对启发式搜索算法中的g(n)和h(n)做如下限制:
①g(n)是对g*(n)的估计,且g(n)>0;
②h(n)是h*(n)的下界,即对任意节点n均有h(n)≤h*(n)。
在满足上述条件情况下的有序搜索算法称为A*算法。
对于某一搜索算法,当最佳路径存在时,就一定能找到它,则称此算法是可纳的。可以证明,A*算法是可纳算法。也就是说,对于有序搜索算法,当满足h(n)≤h*(n)条件时,只要最佳路径存在,就一定能找出这条路径。
A* 算法的优劣性主要是靠评估函数的启发能力上,对于八数码问题
f*(n)=g*(n)+h*(n)
g*(n)是可以确定的,对于h*(n)启发函数根据任意结点与目标之间的差异定义,例如取h(n)=w(n),我们很容易知道,尽管对具体的h*(n)是多少我们不知道,但根据“不在位”将牌个数这个估计,就能得出至少要移动多少步才能到达目标。如果启动函数根据考虑到任意结点与目标之间的距离的信息,例如取h(n)=P(n),P(n)定义为每一个将牌与其目标位置之间的距离总和
例如:
任意状态s 7 5 3
1 6 4
2 8 0
目标状态g 1 2 3
8 0 4
7 6 5
中任意状态s中将牌 7 (1,1) 与目标状态 7 (3,1)距离为 L=|3-1|+|1-1|=2
P(n)=每一个将牌与其目标位置之间的距离总和,
此外,我们还发现
1 2 3 1 2 3
8 0 4 4 0 8
7 6 5 7 6 5
h(n)=P(n)不能估计出相邻两个将牌位置难易程度的影响,为此我们再引入S(n)分量。S(n)是对结点n中将牌排列顺序的计分值,规定对非中心将牌,顺某一方向检查,若某一将牌后继将牌和目标状态所对应顺序不一致,这该将牌分值为2,一致为0;对于中心位置有将牌智估分值为1,无将牌(值为0时)估分值为0;对于非中心位置,每一个将牌估分值总和加上中心位置估分值定义为S(n)。
依据这些启发信息,取:
h(n)=P(n)+3*S(n)
根据需要open表和close表记录结点信息。OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。同时open表和close表里面所包含的结点状态不重复。每一个结点需包含以下属性:
Struck{
父结点 数据(应为open表和close表数据唯一性可根据父节点信息找到父结点)prev
本身数据 con
总访问次数num
初始结点到该节点最小代价g(n)
按顺序 每一个将牌估分值总和加上中心位置估分值s(n)
最终估计函数所得值也就是到达目标估计代价f(n)=g(n)+h(n)=g(n)+ P(n)+3*S(n)
}
将要扩展的结点添加到close 进行扩展结点 将扩展结点添加到open里面,同时后从将该扩展结点代价值设置为无穷大(inf)
继续进行扩展结点 选择代价为最小的进行扩展将要扩展的结点添加到close 进行扩展结点 将扩展结点添加到open里面,同时后从将该扩展结点代价值设置为无穷大(inf)
继续扩展代价最小的结点
将要扩展的结点添加到close 进行扩展结点 将扩展结点添加到open里面,同时后从将该扩展结点代价值设置为无穷大(inf)
如此循环扩展open表里面最小代价结点直到找到目标
我使用的是matlab 进行编程这是考虑matlab具有强大的功能,相对于c++、C 、java等其他语言它不需要定义任何变量,语言也是非常简单的
初始化
dis=[1 2 3;8 0 4;7 6 5];目标节点
//初始结点
f.prev=zeros(3);
f.con=[7 5 3;1 6 4;2 8 0];
f.num=1;
f.g=0;
f.s=gufei(f,dis);
f.fuc=valuefuc(f,dis);
y=dis;
将f结点添加到open表中
Open(1)=f
While{
for 找到open表中代价最小的节点 open(j)
将open(j)赋值给k
将k添加到close中
将open(j)从open中进行删除。
对结点k(代价最小结点)进行扩展结点
{
根据结点本身数据信息可得到相应的扩展结点
例如:
根据0 位于(3,3)可向上移或者右移
如得到
初始化该节点相关数据
将该节点加入到open表中
遍历open表删除相同结点(及查询刚刚添加的结点是否已经存在,存在则删除)
}
If close表里面时候存在目标结点
根据close表输出最短路径
}
三、 程序代码:
<span style="font-size:18px;">function []=bashuma
global e; %open表计数
global i; %close表计数
global m; %循环次数计数
a=0;b=0;n=0;i=0;
e=1;m=0;
%%
% *BOLD TEXT*
%初始化
dis=[1 2 3;8 0 4;7 6 5]; %目标节点
f.prev=zeros(3);
f.con=[7 5 3;1 6 4;2 8 0]; %初始节点
f.num=1;
f.g=0;
f.s=gufei(f,dis);
f.fuc=valuefuc(f,dis);
y=dis;
open(1)=f; %初始化Open表
k=f;
while a==0
m=m+1;
%设置循环次数
if m>=10000
disp('error!!');
break;
end
% 寻找代价最小值
for j=1:e
if k.fuc>open(j).fuc
k=open(j);
end
end
%%添CLOSE表
i=i+1;
j=k.num;
close(i)=k;
close(i).num=i;
%%删OPEN表
open(j).con=zeros(3);
open(j).fuc=inf;
%%是否找到目的节点
a=getit(close,dis);
if a==1
disp('success!!');
break;
end
%%扩展节点
open=opera(open,close,k,dis);
k.fuc=inf;
end
%输出路径
while 1
for j=1:i
if y==zeros(3)
b=1;
break;
end
if close(j).con==y
t=close(j).num;
n=n+1;
show(n)=t;
y=close(j).prev;
end
end
if b==1
break;
end
end
for j=1:n
close(show(n+1-j)).con
end
end
%查询是否找到目标节点
function a=getit(close,dis)
global i;
for j=1:i
if close(j).con==dis
a=1;
break;
else
a=0;
end
end
end </span>
<span style="font-size:18px;">function ss= gufei( f,dis )
%UNTITLED5 Summary of this function goes here
% Detailed explanation goes here
s=0;
if f.con(2,2)~=0
s=s+1;
end
j=f.con(1,1);
if j~=0
if j==8
if f.con(1,2)~=1
s=s+2;
end
end
if j~=8
if f.con(1,2)~=j+1
s=s+2;
end
end
end
j=f.con(1,2);
if j~=0
if j==8
if f.con(1,3)~=1
s=s+2;
end
end
if j~=8
if f.con(1,3)~=j+1
s=s+2;
end
end
end
j=f.con(1,3);
if j~=0
if j==8
if f.con(2,3)~=1
s=s+2;
end
end
if j~=8
if f.con(2,3)~=j+1
s=s+2;
end
end
end
j=f.con(2,3);
if j~=0
if j==8
if f.con(3,3)~=1
s=s+2;
end
end
if j~=8
if f.con(3,3)~=j+1
s=s+2;
end
end
end
j=f.con(3,3);
if j~=0
if j==8
if f.con(3,2)~=1
s=s+2;
end
end
if j~=8
if f.con(3,2)~=j+1
s=s+2;
end
end
end
j=f.con(3,2);
if j~=0
if j==8
if f.con(3,1)~=1
s=s+2;
end
end
if j~=8
if f.con(3,1)~=j+1
s=s+2;
end
end
end
j=f.con(3,1);
if j~=0
if j==8
if f.con(2,1)~=1
s=s+2;
end
end
if j~=8
if f.con(2,1)~=j+1
s=s+2;
end
end
end
j=f.con(2,1);
if j~=0
if j==8
if f.con(1,1)~=1
s=s+2;
end
end
if j~=8
if f.con(1,1)~=j+1
s=s+2;
end
end
end
ss=s;
end
</span>
<span style="font-size:18px;">%上下左右移动操作
function open=opera(op,cl,f,dis)
global i;
[x,y]=find(f.con==0);
if x==1&&y==1
open=rt(f,op,cl,dis);
open=dn(f,open,cl,dis);
elseif x==1&&y==2
open=lt(f,op,cl,dis);
open=rt(f,open,cl,dis);
open=dn(f,open,cl,dis);
elseif x==1&&y==3
open=lt(f,op,cl,dis);
open=dn(f,open,cl,dis);
elseif x==2&&y==1
open=up(f,op,cl,dis);
open=rt(f,open,cl,dis);
open=dn(f,open,cl,dis);
elseif x==2&&y==2
open=lt(f,op,cl,dis);
open=up(f,open,cl,dis);
open=rt(f,open,cl,dis);
open=dn(f,open,cl,dis);
elseif x==2&&y==3
open=lt(f,op,cl,dis);
open=up(f,open,cl,dis);
open=dn(f,open,cl,dis);
elseif x==3&&y==1
open=up(f,op,cl,dis);
open=rt(f,open,cl,dis);
elseif x==3&&y==2
open=lt(f,op,cl,dis);
open=up(f,open,cl,dis);
open=rt(f,open,cl,dis);
elseif x==3&&y==3
open=lt(f,op,cl,dis);
open=up(f,open,cl,dis);
end
end
function open=rt(f,op,cl,dis)
global e;
e=e+1;
s=f;
[x,y]=find(s.con==0);
t=s.con(x,y+1);
s.con(x,y+1)=0;
s.con(x,y)=t;
s.num=e;
op(e).con=s.con;
op(e).prev=f.con;
op(e).num=e;
op(e).fuc=valuefuc(s,dis);
op(e).g=f.g+1;
op(e).s=gufei(s,dis);
search(s,op,cl);
open=op;
end
function open=up(f,op,cl,dis)
global e;
e=e+1;
s=f;
[x,y]=find(s.con==0);
t=s.con(x-1,y);
s.con(x-1,y)=0;
s.con(x,y)=t;
s.num=e;
op(e).con=s.con;
op(e).prev=f.con;
op(e).num=e;
op(e).fuc=valuefuc(s,dis);
op(e).g=f.g+1;
op(e).s=gufei(s,dis);
search(s,op,cl);
open=op;
end
function open=lt(f,op,cl,dis)
global e;
e=e+1;
s=f;
[x,y]=find(s.con==0);
t=s.con(x,y-1);
s.con(x,y-1)=0;
s.con(x,y)=t;
s.num=e;
op(e).con=s.con;
op(e).prev=f.con;
op(e).num=e;
op(e).fuc=valuefuc(s,dis);
op(e).g=f.g+1;
op(e).s=gufei(s,dis);
search(s,op,cl);
open=op;
end
function open=dn(f,op,cl,dis)
global e;
e=e+1;
s=f;
[x,y]=find(s.con==0);
t=s.con(x+1,y);
s.con(x+1,y)=0;
s.con(x,y)=t;
s.num=e;
op(e).con=s.con;
op(e).prev=f.con;
op(e).num=e;
op(e).fuc=valuefuc(s,dis);
op(e).g=f.g+1;
op(e).s=gufei(s,dis);
search(s,op,cl);
open=op;
end
function fuc=valuefuc(f,dis)
f.s=gufei(f,dis);
s1=f.g+3*f.s;
[x,y]=find(f.con==1);
s1=s1+abs(x-1)+abs(y-1);
[x,y]=find(f.con==2);
s1=s1+abs(x-1)+abs(y-2);
[x,y]=find(f.con==3);
s1=s1+abs(x-1)+abs(y-3);
[x,y]=find(f.con==4);
s1=s1+abs(x-2)+abs(y-3);
[x,y]=find(f.con==5);
s1=s1+abs(x-3)+abs(y-3);
[x,y]=find(f.con==6);
s1=s1+abs(x-3)+abs(y-2);
[x,y]=find(f.con==7);
s1=s1+abs(x-3)+abs(y-1);
[x,y]=find(f.con==8);
s1=s1+abs(x-2)+abs(y-1);
fuc=s1;
end </span>
<span style="font-size:18px;">% 查询重复操作
function []=search(f,op,cl)
global e;
global i;
%e
%open(e-1).con
for j=1:e-1
if op(j).con==f.con
e=e-1;
break
end
end
for j=1:i-1
if cl(j).con==f.con
e=e-1;
break;
end
end
end </span>
-> -> ->
五、学习心得:
这个算法自己慢慢看书,从刚开始接触这到底是什么鬼呀,到原来如此,最后到算法优化,整体感觉还是挺不错的。这个算法值得注意和思考的问题就是 评估函数 ,评估函数直接影响到你的A*算法的效率,刚开始我选取的h(n)为p(n)------(每一个将牌与其目标位置之间的距离总和)运行时间较为慢需要6秒左右,后面加上S(n) ------ (每一个将牌估分值总和加上中心位置估分值)耗时只要1.5秒。
这是时隔好久才开始在csdn上发表博客了,以后继续要加油啊。
参考博客
http://blog.csdn.net/damotiansheng/article/details/40017107
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。