搜索算法—A*
这东西,简直是被神化了一样。我从蓝书上查找了一些资料,以我的水平确实难以看懂他的表达。于是借助百度,发现大多数博客都在转载一篇似乎是翻译自国外的文章。写的很详细,但我在第一次阅读的时候并没有看懂,并且感觉专业性较高。比如其中又是OpenList又是CloseList的,初看确实有些头晕眼花。
那么,在这里重新整理一下我理解A*的过程。
概念理论
这个算法似乎被广泛应用于游戏开发的寻路算法里。他是启发式搜索的算法。相比于传统的搜索,你可以理解为他是边搜边看和终点的偏差的,也就是他自己在尽可能修正自己的路线的感觉。
那么怎么实现这个“让程序自我认识并修正路线”呢?
我们引入一个经典的成语故事。
话说,南辕北辙…xxxxxxxx
是的,如果一个搜索算法的路径如同南辕北辙一样,其效率一定是极其低下的。那么在选择移动的时候,我们每次都要尽可能的去选一个靠近终点的移动。那么如何让程序知道某个点更靠近于终点呢?
这里就要引入一个估价的概念。
估价函数,它所实现的功能即为:以任意状态的输入,计算出该状态到目标状态产生代价的估计值。
通过这个估价,我们让程序实现两个考虑点。“考虑中间某个点与初始点的距离”,“考虑中间某个点与终点的距离”。当两者之和最小时,最短路径也就自然产生。
听完这些口胡(口头胡扯233),就可以去看一下大佬的文章了。
还能复习一波深广搜,多棒唷233
看完这个可能虽然有了感觉,但还是觉得有些东西没被点明白。没关系,这个时候去进攻那个国外的文章,就轻松加愉快了。
这里面包含了A*算法的模拟过程,相当详细,大佬很强啊!
小做整理
由于转载进来的东西太多了…所以简单梳理一下。
首先我们要维护几个东西:
Open List:需要不断去处理的“受关注”的点列表
Close LIst:已经“不需要受到关注”的点列表
可能需要一个二叉堆,便于之后的“总是取出F最小值”的操作。
完成一个功能:
预估路径->这个功能可以求出的内容:
F:最终预估值,有F=G+H
G:从起点 A 移动到指定方格的移动代价
H:从指定的方格移动到终点 B 的估算成本
结束时的状态:
目标点被加入OpenList。
或
查找终点失败,并且 open list 是空的,此时没有路径。
如何去维护两个List?/两个List的关系是什么?
在初始的时候,我们将起点加入OpenList。
在每次进行搜索的时候,都会取OpenList中F值最小的点做处理,然后将其移入CloseList。
对最F最小点做了什么处理?
对于被取出的F值最小的点,对其周边点进行如下处理(A、B、C中只择其一):
A.当该周边点是不可达的(即该点为障碍物,不可被作为路径点)或存在于CloseList中时:
忽略,不进行任何处理。
B.当该周边点尚未处于OpenList中时:
把它加入 Open List ,并且把当前方格设置为它的父亲(如果你需要具体路径的话),记录该方格的 F , G 和 H 值。
C.该周边点已经位于OpenList中时:
我们假设该节点的父节点被重载为F值最小点。用因为这个假设而产生的新的G值来与以起点为父节点产生的旧的G值做对比。如果新G值更小,那么更换该周边点的父节点,并为其重新计算F、G、H。
如何去实现预估功能?
这里仅仅介绍一种。
这个距离算法,忽略障碍物,不斜着走。就是一道横线一道竖线通往终点。计算公式:d(i,j)=|xi-xj|+|yi-yj|
一般用途
k短路
为了找代码,我在网上游荡了很久。发觉更多的都是被融在求第k短路径。虽然不知道为什么,但为了能方便呈现出A*的板子代码,这里决定先行分析现有资源
什么是k短路问题?
在研究学习过程中,发现这个东西是涉及到几个比较需要动脑子的知识点的。
因为这里涉及一个预处理路径的问题,所以我发现网上有人用SPFA,有人用dijkstra。那么各取所好吧。反正我觉得Dj用的频率好像多一点…
题目中的Show Time
魔法猪学院 SDOI2010
据说LuoGu加强了数据,导致A*会有一个点被卡掉。但这个对我们不产生影响,因为我们只是想拿这道题来熟悉一下A*的代码实现而已。
----7.23补档
关于A*,我已经研究了整整三天了。今天我终于悟到了这道题的写法(虽然代码依然不是我自己写的2333),但我前前后后写了有四个板子去试去撞。撞了一排又一排的WonderfulAnswer。然后今天的模拟赛我还又打了一手A*试了试,难得写对了跑掉了样例。(然而那道题用A*的话时间爆炸…一分都拿不到)。
诉苦结束。说几个注意事项:
跑Spfa的时候,是用反图跑的。你需要把边反转过来,而不是用正边跑。否则会得到一个满满的除了初始点是0其他都是初始化的距离数组。
这道题不是经典无脑模板题,依然需要你手动去改一点东西。
下面的代码有一些小不一样。在这里,我们用Spfa预处理的就是该点到终点的距离H。然而因为这代码不是我写的,所以这代码里的h是指代节点与出发点的距离。大家做一下区分。
#include<bits/stdc++.h> using namespace std; int g,n,m,head[5005],tail[5005],ans,bj[200005]; double ch[5005],e; struct node{ int to,n; double w; }pr[200005],rpr[200005]; struct data{ int d; double h,f; bool operator < (const data &a) const { return f>a.f; } }zz,mm; priority_queue <data> q; queue <int> p; void add(int u,int v,double w){ pr[++g].w=w; pr[g].to=v; pr[g].n=head[u]; head[u]=g; rpr[g].w=w; rpr[g].to=u; rpr[g].n=tail[v]; tail[v]=g; } void spfa() { memset(ch,0x7f,sizeof(ch)); bj[n]=1; ch[n]=0; p.push(n); while(!p.empty()) { int u=p.front(); p.pop(); bj[u]=0; for(int i=tail[u];i;i=rpr[i].n) { int v=rpr[i].to; double z=rpr[i].w; if(ch[u]+z<ch[v]) { ch[v]=ch[u]+z; if(!bj[v])bj[v]=1,p.push(v); } } } } void A(){ zz.d=1,zz.h=0,zz.f=0; q.push(zz); while(!q.empty()) { zz=q.top();q.pop(); if(zz.f>e)return; int u=zz.d; if(u==n) { e-=zz.f;ans++;continue; } for(int i=head[u];i;i=pr[i].n) { mm.d=pr[i].to; mm.h=zz.h+pr[i].w; mm.f=mm.h+ch[pr[i].to]; q.push(mm); } } } int main() { cin>>n>>m; scanf("%lf",&e); for(int i=1;i<=m;i++) { int x,y;cin>>x>>y; double z;scanf("%lf",&z); add(x,y,z); } spfa(); A(); printf("%d",ans); return 0; }