搜索专题——A*算法

本文详细介绍了A*算法的工作原理,包括F(n)、G(n)和H(n)的定义,以及如何通过启发函数优化宽搜。通过K短路为例,展示了如何在Dijkstra算法基础上利用启发式距离加速找到最短路径。特别强调了启发函数的可采纳性和选择的重要性。
摘要由CSDN通过智能技术生成

什么是A*算法?

A*算法是一种结合了启发函数的宽搜算法,当搜索空间巨大时,我们可以为宽搜加上一个启发函数,来降低搜索的时空复杂度。

A*算法的原理是什么?

在A*算法中:

定义 F ( n ) F(n) F(n) 为搜索树上节点的估计函数,我们根据估价的大小选择节点扩展的优先级(例如,在K短路算法中,估价值越小,越优先)

定义 G ( n ) G(n) G(n) 为根节点到每个节点的实际代价值(例如,在K短路算法中,该函数的意义为起始点到每个点的路径长度)

定义 H ( n ) H(n) H(n) 为当前节点到目标节点的一个启发式估计值,作为我们优化宽搜的一个关键参考(例如,在K短路算法中,该函数的意义为当前节点到目标节点的最短距离)

因此: F ( n ) = G ( n ) + H ( n ) F(n)=G(n)+H(n) F(n)=G(n)+H(n)

Attention1:启发函数唯一的作用,只是改变了状态的搜索顺序,优先搜索最有可能趋近于答案的状态,从而减小搜索空间

Attention2:启发函数必须要可采纳,什么样的启发函数是可采纳的呢?即启发函数的值必须要小于等于当前节点到目标节点的一个实际值(简单理解一下,如果启发函数值大于实际值,则启发函数反而帮了倒忙,可能会造成错误,我们宁可使用朴素算法,也不能出现错误

Attention3:启发函数的选择很重要,在可采纳的前提下,一个启发函数越相似于真实值,越有效。

例题1:K短路

在这里插入图片描述注解1:首先不考虑时间和空间,我们怎样求解K短路?

在Dijkstra中,我们用 s t [ v e r ] st[ver] st[ver] 数组控制节点的扩展次数,由贪心思想可知,当节点第一次出堆时,此时一定时最短路(可以用贪心的思想简单理解一下,严格的数学证明可以啃算导,好像是数学归纳法证明)。同理,我们去掉 s t st st 数组,允许一个节点多次扩展,则节点第 K K K 次出堆就是 K K K 短路(也可以用贪心思想理解,即当前的路径值是非减的(无负权前提),初学者没必要花大量时间去严格证明)

注解2:求解K短路用启发函数优化的合理性是什么?
为什么加上这个启发函数能够保证正确性?首先,我们选取当前节点到目标节点的距离作为启发函数,根据启发函数的可采纳性,我们知道这样是正确的。还是不放心?那我们结合本题来想象一个场景:我们可以根据时间顺序,把出堆的节点从前到后排成一排,则对于其中的 T T T 节点(所有的 H ( T ) = 0 H(T)=0 H(T)=0),则前面的 T T T 节点的 G ( T ) G(T) G(T) 的值一定小于等于后面的,所以一定是 K K K 短路。

注解3:看了好多题解,以及算法进阶指南上的剪枝说法也是错的(即非终点节点扩展K次后不再扩展,这种做法是错的,不用严格证明,简单理解一下也感觉没得科学性,也是宁缺勿错)

注解4:所以本题,在反图上跑Dijkstra,用终点到当前节点的距离作为启发函数。

易错点:
1.当 S = = T S==T S==T 时,根据题目要求要 K + + K++ K++
2.当 S S S T T T 之间不连通时,要特判,否则死循环
3.由于启发函数只是针对 T T T 节点的,所以别的出堆次数并不能反映其的 K K K 短路情况!!!(而朴素算法是可以的)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<map>
#include<unordered_map>
#include<queue>

#define me(x,y) memset(x,y,sizeof x)
#define rep(i,x,y) for(i=x;i<=y;++i)
#define lowbit(x) -x&x
#define inf 0x3f3f3f3f
#define INF 0x7fffffff
#define cu(x) cout<<x<<"--"<<endl
#define ex exit(0)
#define pn puts("")

using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef vector<int> vc;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

struct edge
{
    int to,nxt,val;
};

struct pii
{
    int tag,fx,gx;  //fx为估价值,gx为从起点到当前点的真实值,fx=gx+hx
};

bool operator>(const pii& z1,const pii& z2)         //注意,小根堆要重载大于号
{
    if(z1.fx==z2.fx)    return z1.gx>z2.gx;
    return z1.fx>z2.fx;
}

const int N =1010,M=2e4+10;
int n,m,s,t,k,idx;

int head[N],headf[N];
edge table[M];          //有向边,但是要建立反向图
int dist[N];            //dijkstra跑的反向图
bool st[N];

inline void add(int head[],int& u,int& v,int& w)
{
    table[++idx].nxt=head[u];
    head[u]=idx;
    table[idx].to=v;
    table[idx].val=w;
}

void dijkstra()
{
    int u,v,e,w;
    
    priority_queue<PII,vector<PII>,greater<PII>>q;
    me(dist,0x3f);
    dist[t]=0;
    q.push(PII{0,t});
    
    while(!q.empty())
    {
        auto x=q.top();
        q.pop();
        u=x.second;
        
        if(st[u])   continue;
        st[u]=true;
        
        for(e=headf[u];e;e=table[e].nxt)
        {
            v=table[e].to;
            w=table[e].val;
            
            if(dist[v]>dist[u]+w)
            {
                dist[v]=dist[u]+w;
                q.push(PII{dist[v],v});
            }
        }
    }
}

int A_star()
{
    int u,v,e,w,cnt=0;
    
    if(dist[s]==inf)    return -1;
    
    priority_queue<pii,vector<pii>,greater<pii>>q;
    q.push({s,dist[s],0});
    
    while(!q.empty())
    {
        auto x=q.top();
        q.pop();
        u=x.tag,w=x.gx;
        
        if(u==t)    ++cnt;
        if(cnt==k)  return w;
        
        for(e=head[u];e;e=table[e].nxt)
        {
            v=table[e].to;
            q.push({v,w+table[e].val+dist[v],w+table[e].val});
        }
    }
    
    return -1;
}

int main()
{
    int i,u,v,w;
    
    n=read(),m=read();
    rep(i,1,m)
    {
        u=read(),v=read(),w=read();
        add(head,u,v,w);
        add(headf,v,u,w);
    }
    s=read(),t=read(),k=read();
    if(s==t)    ++k;
    
    dijkstra();
    printf("%d",A_star());
	
	return 0;
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值