蒟蒻谈A*

Posted nevereasy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了蒟蒻谈A*相关的知识,希望对你有一定的参考价值。

本文章只表达本人对A*的一些见解和看法,如果有写错的地方,或者你有不同的观点你来打我啊,欢迎指出
只讲结点间的计算,不讲网格间的计算
我们先谈谈为什么要用A*
当我们找东西的时候
我们一般不会漫无目的的到处找
而是有目标的去找
A*就是有目标的去找
A*有时候比其他搜索更快就是有目标的去寻找(也就是启发式函数)

A*看上去很难,而网上有些文章里的代码也真的很长很难理解

但是

那些很长很长、定义一个头文件(关于C++的文章)、用很多看都没看过的函数的文章
大多是关于工程设计(游戏什么的),与我们OI没有很大的关系
A*算法就是结合 Dijkstra算法和 的最佳优先搜索一个神奇的算法
最佳优先搜索好像不常见
如果不会这两个的朋友可以先去学习一下
简单点的 A*就是 优先队列+估价函数
网上很多文章都提到了A*算法最重要的是启发式函数(好像就是估价函数)
F=G+H
F 决定下一步走的位置
G 表示从起点到指定位置的距离
H 表示从指定位置到终点的预计距离
接下来分析几种情况:
当H(n)=0时,那么算法会退化成Dijkstra,虽然还是能找到最短路,但是会变慢
当H(n)比G(n)大许多时,则算法会退化成BFS(最佳优先搜索)不是宽度优先搜索!!!
当H(n)越小时,搜索到的节点就越多,搜索所用的时间也越长,但是能保证找到一条最短的路径
而H(n)越大时,搜索到的节点就越少,搜索所用的时间也会随之减少,但是不能保证找到最短的路劲
所以运用的时候一定要注意了
对启发式函数讲得详细的文章贴在本文末尾了才不是因为语文不好讲不清楚
A*的具体做法:
1.将起点S存入开放列表//开放列表:储存结点的优先队列或堆
2.将与起点S连接的结点放入开放列表,将起点设为其他结点的父结点
3.将起点S从开放列表中取出,放入关闭列表//关闭列表:储存不需要继续检查的结点的数组(这里就没必要用优先队列了)有时候没有也没关系
4.选择一个开放列表中F值最小的结点B,将B从开放列表中取出,放入关闭列表其实只要取出就可以了,关闭列表无所谓的
5.将与B相连的且未在开放列表中的结点放入开放列表
6.如果到达终点就退出,否则返回4

A*的函数与dijkstra的函数差不多
只是少了个vis数组
为什么要这样做呢?
因为我们走完最短路还要走第2短路,第3短路等等
所以我们得在优先队列中存好之前走的距离
根据我们画一个优先队列示意图和结点图,便于理解

看到这里是不是感觉很迷?
关于A*的题目,比较经典的就是K短路问题了(因为我只看到K短路,其实一般的最短路也能做)
dalao说A*可以做很多路径搜索的题目,但那样就直接退化成了DIjkstra
在k短路问题中
我们可以先用dijkstra或者spfa来求出每个点的H值
我们来做一道题目,加深理解(更迷)~~
然而某谷上只有两个K短路问题,而且有一个还卡A*,要加一个很长的特判才能过
所以我们用POJ上的题目Remmarguts‘ Date
这是洛谷上的K短路
才不是因为不会字符串才不讲
题目大意:
让你在有n个点m条边的有向图
找到s结点到t结点的第k短路,如果找不到就输出-1
有多组数据
输入数据:
第1行两个数n,m
第2~m+1行,每行3个数u,v,w,表示结点u到结点v有一条距离为w的结点
第m+2行,3个数s,t,k,表示求结点s到结点t的第k短路的距离
输出数据
一行,结点s到结点t的k短路的距离

样例输入
2 2
1 2 5
2 1 4
1 2 2

样例输出
14
技术图片
图画得好丑

从图中可以看出,
1到2的最短路是 1->2 距离为5
第2短路是 1->2->1->2 距离为5+4+5=14
所以答案是14

#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
#define re register
#define isnum(x) (x<58&&x>47)
const int maxn=100001;
const int INF=2147383647;
struct edge
    int to,nxt,w;
z[maxn<<1],f[maxn<<1];
struct node
    int to,g,f;
    bool operator <(const node& x) const 
        if(!(x.f^f))return g<x.g;
        return f>x.f;
    
;
int n,m,cnt,tot;
int s,t,k;
int head1[maxn<<1],head2[maxn<<1],dis[maxn<<1],vis[maxn<<1];
inline void add(int u,int v,int w)
    cnt++;
    z[cnt].to=v;
    z[cnt].nxt=head1[u];
    z[cnt].w=w;
    head1[u]=cnt;
    f[cnt].to=u;//反向建边
    f[cnt].nxt=head2[v];
    f[cnt].w=w;
    head2[v]=cnt;

inline void dijkstra(int start)
    for(re int i=1;i<=m;i++)
        dis[i]=INF;
    dis[start]=0;
    vis[start]=1;
    queue<int>q;
    q.push(start);
    while(!q.empty())
        int v=q.front();
        q.pop();
        int d=dis[v];
        vis[v]=0;
        for(re int i=head2[v];i!=-1;i=f[i].nxt)
            int u=f[i].to,d=f[i].w;
            if(dis[u]>dis[v]+d)
                dis[u]=dis[v]+d;
                if(!vis[u])
                    q.push(u);
                    vis[u]=1;
                
            
        
    

inline int A_star()
    if(!(dis[s]^INF))
        return -1;
    
    if(!(s^t))k++;
    priority_queue<node>q;
    node y;
    y.to=s;y.g=0;y.f=dis[s];
    q.push(y);
    while(!q.empty())
        node x=q.top();
        //printf("%d %d %d %d\\n",x.f,x.g,x.to,t);
        //getchar();
        q.pop();
        if(!(x.to^t))
            tot++;
            if(!(tot^k))
                return x.g;
            
        
        for(re int i=head1[x.to];i!=-1;i=z[i].nxt)
            int v=z[i].to,g=x.g+z[i].w,f=g+dis[v];
            y.f=f;y.g=g;y.to=v;
            q.push(y);
        
    
    return -1;

int main()
    while(scanf("%d%d",&n,&m)!=EOF)
        tot=0;
        cnt=0;
        memset(z,0,sizeof(z));
        memset(f,0,sizeof(f));
        memset(head1,-1,sizeof(head1));
        memset(head2,-1,sizeof(head2));
        memset(vis,0,sizeof(vis));
        for(re int i=1;i<=m;i++)
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
        
        scanf("%d%d%d",&s,&t,&k);
        dijkstra(t);
        printf("%d\\n",A_star());    
    
    return 0;

难道A*这么强的算法只能做k短路吗?

当然不是还能做游戏AI

它还可以用来做8数码问题15数码也行

在这类题目中A*的估价函数与图论中的思想大同小异,代码却截然不同

这类题目的估价函数依然是距离目标的步数

但是记录的是现在的情况与目标情况有多少种不同

下面是P1379 八数码难题的代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
#define re register
#define isnum(x) (x<58&&x>47)
const int goal[4][4]=
    3,1415926535,897932384,626433832,
    79502884,1,2,3,
    19716939,8,0,4,
    93751058,7,6,5
;
const int dx[4]=0,1,-1,0,dy[4]=1,0,0,-1;
int k,sx,sy,flag;
int map[4][4];
inline int check()
    for(re int i=1;i<=3;i++)
        for(re int j=1;j<=3;j++)
            if(map[i][j]^goal[i][j])
                return 0;
            
    
    return 1;

inline int count(int step)
    int cnt=0;
    for(re int i=1;i<=3;i++)
        for(re int j=1;j<=3;j++)
            //cnt+=map[i][j]^goal[i][j]?1:0;
            if(map[i][j]^goal[i][j])
                if(++cnt+step>k)
                    return 0;
                
            
        
    
    return 1;

inline void _swap(int &a,int &b)
    int t=a;a=b;b=t;

inline void A_star(int step,int x,int y,int pre)
    if(flag)return ;
    if(!(step^k))
        if(check())flag=1;
        return ;
    
    for(re int i=0;i<=3;i++)
        int xx=x+dx[i],yy=y+dy[i];
        if(xx>3||yy>3||xx<1||yy<1||!((i+pre)^3))           
            continue;
        
        _swap(map[xx][yy],map[x][y]);
        if(count(step)&&!flag)A_star(step+1,xx,yy,i);
        _swap(map[xx][yy],map[x][y]);
    

char ch[11];
int main()
    scanf("%s",ch+1);
    for(re int i=1;i<=9;i++)
        int xx=(i-1)/3+1,yy=(i-1)%3+1;
        map[xx][yy]=(ch[i]^48);
        if(!(ch[i]^48))sx=xx,sy=yy;
    
    if(check())
        puts("0");
        return 0;
    
    while(++k)
        A_star(0,sx,sy,-1);
        if(flag)
            printf("%d\\n",k);
            return 0;
        
    

以上是关于蒟蒻谈A*的主要内容,如果未能解决你的问题,请参考以下文章

蒟蒻修炼计划-KMP 模板

BZOJ4636蒟蒻的数列 STL

置换群(本蒟蒻瞎BB的)(未完)

bzoj4916 神犇和蒟蒻

[BZOJ4916]神犇和蒟蒻

bzoj4636: 蒟蒻的数列