引入
在学这个A*算法的时候,必须先回顾一下前面提过的优先队列BFS算法,这个算法维护了一个优先队列,不断从堆中取一个“当前代价最小的”状态,然后在取出来的时候就是得到了这个状态的最小代价
说白了,这些乱七八糟的算法都是根据 一句话诞生的
时间就是一切
这些“迭代加深”,“A*”等算法都是对DFS BFS的一个优化
这个A算法就是在路径查找的时候,计算各个顶点的实际权重,以及各个顶点的推测权重,=两个因素,可以有效地减少不必要地操作,从而提高查找效率
也就是说靠着推测进行走,不会多走路,走冤枉路
A算法同别的算法不同的就是,它会预先设置一个推测权重,并在查找的过程中推测权重一并纳入决定最短路径可以更有效地查找最短路径,也就是根据事先的条件给定一个预估值,结合这个预估值来进行查找
优先队列BFS
就是说用优先队列(优先堆)+BFS,也就说当一个搜索图存在两种不同的代价,那么我们把小的代价存在优先队列的队首,那么大一点的就放在后面,那么不断地取当前最小代价进行拓展,这样最后就去到了最小代价
可是这种做法有点类似于贪心,我们只是保存了当前的局部最优解,这样显然不完美,当前的代价最优不是说全局最优;当前的代价最差不一定说全局最差。
A*搜索
又叫启发式算法
启发式策略,可以通过指导搜索向最有希望的方向前进,降低了复杂性。通过删除某些状态及其延伸,启发式算法可以消除爆炸,并得到令人能接受的解(通常并不一定是最佳解)。
然而,启发式策略是极易出错的。在解决问题的过程中启发仅仅是下一步将要采取措施的一个猜想,常常根据经验和直觉来判断。由于启发式搜索只有有限的信息(比如当前状态的描述),要想预测进一步搜索过程中状态空间的具体行为则很难。
一个启发式搜索可能得到一个次最佳解,也可能一无所获。
以上是对“优先队列BFS”和“启发式搜索”的补充
接下来我们得学习一下A搜索
为了提高搜索效率,可以对未来可能产生的代价进行一个预估,减少不必要的步数
详细的步骤:首先设计一个“估计函数”,从任意状态为输入,直接计算该状态到目标状态的总估计值。我们仍然需要维护一个堆,不断从堆里面取出“当前代价+未来估值”最小的状态进行拓展
所以A算法的难点就在于如何设计这个估计函数
首先,必须满足一个准则,就是说:当前的状态为s,设f(s)为估计值,那么最后未来通过搜索的确值g(s)必须满足f(s)<=g(s)
也就是说,估计函数不能大于未来的实际代价,不然就不满足优化了
所以在目标被取出之前的某个时刻:
1.根据s并非最优,s的当前代价就会大于从起始状态到目标状态的最小代价
2.对于最优解搜索路径上的状态t,根据f(t)<=g(t),t的当前代价加上f(t)必定小于等于t的代价加上g(t)
结合上面两点t的当前代价加上f(t)小于s的当前代价,所以t就会被堆顶取出进行拓展,最终更新目标状态
这种带有估价函数的优先队列BFS就是A搜索算法,只要保证任意一个时刻状态s,都有f(s)<=g(s),A算法就能在目标状态第一次从堆中取出的时候得到最优解,并且只会拓展一次,所以说f越准确、接近g数组,算法的效率就越高,如果估价始终为零,就和普通的BFS差不多
所以从这里可以看出,A*算法的关键在于,能否设计出来一个优秀的估计函数。估计函数尽可能地反映出来实际变化趋势和相对大小关系
个人理解的A*搜索,其实就是说在BFS的时候只需要用估计函数来走步数,有点像动态规划和倍增的结合体,不走冤枉路
这里为了方便阅读,将八数码的代码拿过来
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
char ss[15];
int ans[4][4]=
{//目标状态
{0,0,0,0},
{0,1,2,3},
{0,8,0,4},
{0,7,6,5}
};
int a[5][5];//初始状态
int k,flag;//k为深度 flag为标记
int dx[]={0,1,-1,0};
int dy[]={1,0,0,-1};//方位数组,也是估价函数
bool check()
{//特判函数
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
{
if(ans[i][j]!=a[i][j]) return 0;
}
return 1;
}
bool test(int step)
{//判断是否达到目标
int sum=0;
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
if(ans[i][j]!=a[i][j]&&++sum+step>k)
{
return 0;
}
}
}
return 1;
}
void A_star(int step,int x,int y,int p)
{
if(step==k)//搜索到头了
{
if(check()) flag=1;
return;
}
if(flag) return; //剪枝
for(int i=0;i<4;i++)
{
int vx,vy;
vx=x+dx[i],vy=y+dy[i];//估价步数
if(vx<1||vx>3||vy<1||vy>3||p+i==3) continue;//越界,不符合答案
swap(a[x][y],a[vx][vy]);//交换
if(test(step)&&flag==0) A_star(step+1,vx,vy,i);
swap(a[x][y],a[vx][vy]);//回溯
}
}
int main()
{
int x,y;//存储0的位置
cin>>ss;
for(int i=0;i<9;i++)
{
a[i/3+1][i%3+1]=ss[i]-'0';//存储
if(ss[i]-'0'==0) x=i/3+1,y=i%3+1;
}
if(check())
{
cout<<0<<endl;
return 0;
}
while(++k)//扩大深度
{
A_star(0,x,y,-1);//限制层数
if(flag)
{
cout<<k<<endl;
break;
}
}
return 0;
}

被折叠的 条评论
为什么被折叠?



