1、问题描述
1.1什么是八数码问题
八数码游戏包括一个33的棋盘,棋盘上摆放着8个数字的棋子,留下一个空位。与空位相邻的棋子能够滑动到空位中。游戏的目的是要达到一个特定的目标状态。标注的形式化以下: 初始状态 75 3java
1 64c++
2 80算法
目标状态 1 23编程
80 4bash
7 65函数
1.2 问题的搜索形式描述
状态: 状态描述了8个棋子和空位在棋盘的9个方格上的分布。布局
初始状态: 任何状态均可以被指定为初始状态。学习
操做符: 用来产生4个行动(上下左右移动)。测试
目标测试: 用来检测状态是否能匹配上图的目标布局。优化
路径费用函数:每一步的费用为1,所以整个路径的费用是路径中的步数。
如今任意给定一个初始状态,要求找到一种搜索策略,用尽量少的步数获得上图的目标状态
1.3 算法思想
1.3.1 启发式搜索算法 有序搜索算法(A算法)
启发式搜索算法 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表是一个按节点的启发估价函数值的大小为序排列的一个优先队。
有序搜索算法以下:
一、将初始节点S0放入Open表中;
二、如Open表为空,则搜索失败,退出;
三、把Open表的第一个节点取出,放入到Closed表中,并把该节点记为节点n;
四、若是节点n是目标节点,则搜索成功,求得一个解,退出;
五、扩展节点n,生成一组子节点,对既不在Open表中也不在Closed表中的子节点,计算出相应的估价函数值;
六、把节点n的子节点放到Open表中;
七、对Open表中的各节点按估价函数值从小到大排列;
八、转到2 。
1.3.2启发式搜索算法 A*算法
在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)条件时,只要最佳路径存在,就必定能找出这条路径。
2、问题解析
2.1 A*算法评估函数的分析
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 64
2 80
目标状态g 1 23
80 4
7 65
中任意状态s中将牌 7 (1,1) 与目标状态 7 (3,1)距离为 L=|3-1|+|1-1|=2
P(n)=每个将牌与其目标位置之间的距离总和,
此外,咱们还发现
1 2 31 2 3
80 4 4 0 8
7 6 57 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)
2.2八数码问题解决思路
2.2.1创建open表close表
根据须要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)
}
2.2.2问题算法思路图形展现
总体思路就是根据代价最小进行扩展结点直到找到目标 代价计算钱,前面已经给出了说明。
f(n)=g(n)+h(n)=g(n)+ P(n)+3*S(n)
将要扩展的结点添加到close 进行扩展结点 将扩展结点添加到open里面,同时后从将该扩展结点代价值设置为无穷大(inf)
继续进行扩展结点 选择代价为最小的进行扩展将要扩展的结点添加到close 进行扩展结点 将扩展结点添加到open里面,同时后从将该扩展结点代价值设置为无穷大(inf)
继续扩展代价最小的结点
将要扩展的结点添加到close 进行扩展结点 将扩展结点添加到open里面,同时后从将该扩展结点代价值设置为无穷大(inf)
如此循环扩展open表里面最小代价结点直到找到目标
2.2.3 程序设计思路
我使用的是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表输出最短路径
}
3、 程序代码:
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
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
%上下左右移动操做
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
% 查询重复操做
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
4、运行结果:
->
->
->
5、学习心得:
这个算法本身慢慢看书,从刚开始接触这究竟是什么鬼呀,到原来如此,最后到算法优化,总体感受仍是挺不错的。这个算法值得注意和思考的问题就是评估函数,评估函数直接影响到你的A*算法的效率,刚开始我选取的h(n)为p(n)------(每个将牌与其目标位置之间的距离总和)运行时间较为慢须要6秒左右,后面加上S(n) ------ (每个将牌估分值总和加上中心位置估分值)耗时只要1.5秒。
这是时隔很久才开始在csdn上发表博客了,之后继续要加油啊。
参考博客
/damotiansheng/article/details/40017107