搜索算法—A*

这东西,简直是被神化了一样。我从蓝书上查找了一些资料,以我的水平确实难以看懂他的表达。于是借助百度,发现大多数博客都在转载一篇似乎是翻译自国外的文章。写的很详细,但我在第一次阅读的时候并没有看懂,并且感觉专业性较高。比如其中又是OpenList又是CloseList的,初看确实有些头晕眼花。

那么,在这里重新整理一下我理解A*的过程。

概念理论

这个算法似乎被广泛应用于游戏开发的寻路算法里。他是启发式搜索的算法。相比于传统的搜索,你可以理解为他是边搜边看和终点的偏差的,也就是他自己在尽可能修正自己的路线的感觉。

那么怎么实现这个“让程序自我认识并修正路线”呢?

我们引入一个经典的成语故事。

话说,南辕北辙…xxxxxxxx

是的,如果一个搜索算法的路径如同南辕北辙一样,其效率一定是极其低下的。那么在选择移动的时候,我们每次都要尽可能的去选一个靠近终点的移动。那么如何让程序知道某个点更靠近于终点呢?

这里就要引入一个估价的概念。

估价函数,它所实现的功能即为:以任意状态的输入,计算出该状态到目标状态产生代价的估计值。

通过这个估价,我们让程序实现两个考虑点。“考虑中间某个点与初始点的距离”,“考虑中间某个点与终点的距离”。当两者之和最小时,最短路径也就自然产生。

 听完这些口胡(口头胡扯233),就可以去看一下大佬的文章了。

以红警为例的A*算法讲解

还能复习一波深广搜,多棒唷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。

如何去实现预估功能?

这里仅仅介绍一种。

曼哈顿距离(Manhattan Distance)

这个距离算法,忽略障碍物,不斜着走。就是一道横线一道竖线通往终点。计算公式:d(i,j)=|xi-xj|+|yi-yj|

一般用途

k短路

为了找代码,我在网上游荡了很久。发觉更多的都是被融在求第k短路径。虽然不知道为什么,但为了能方便呈现出A*的板子代码,这里决定先行分析现有资源

什么是k短路问题?

图片来源

在研究学习过程中,发现这个东西是涉及到几个比较需要动脑子的知识点的。

大概是这些:   小根堆    SPFA  

因为这里涉及一个预处理路径的问题,所以我发现网上有人用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;
}
原文地址:https://www.cnblogs.com/Uninstalllingyi/p/11214590.html