浅谈A*算法
一、为什么要用*
在一些最短路问题(爆搜问题)中,我们常常会被高度的时间复杂度卡成,这种时候我们就需要*出场啦
简而言之,*是用来剪枝优化最短路算法和爆搜的时间复杂度的,使得程序可以更快速地得到最优解
二、*的原理
觉得一开始就瞎bb有点不太好
那我们就先拿一道例题入手吧:
我们都知道,在一些最短路算法(如)或中,是要使用到优先队列的
但是一些最短路算法或可能会因为不断遍历很多层而导致空间或者时间的问题以至于原地爆炸
那么我们可不可以对这种算法进行贪心优化呢
我们要求的是短路,我们在进行算法中的一个思想就是贪心,那么我们能不能进行更加确切,更加具有潜力的贪心呢?
我们想要进行贪心,无疑是从一下两个方面去贪心短路的潜力的
当较小,较小时,
那么,也较小,我们就可以拿作为优先队列的优先级进行贪心
但是,我们是不知道的
不知道?那我们就对它进行估价
这就是*的精髓:估价函数
当我们的估价的越精确时,我们也会越精确,就可以更快速地遍历出正确答案
所以,是因题而异的,这也是使用*效率高低的决定性因素,如果你采用了不对的估价方式,那么效果可能会大打折扣
因为是估价出来的,也就是说,是完美状态
换句话说
在 步内不可能到达终点
所以,这也就证明了使用*的正确性
在这道题中,我们采用反向跑最短路来得出
详见代码
#include<bits/stdc++.h>
using namespace std;
const int N=60,M=2600,INF=0x7fffffff;
int n,m,k,s,t,cnt=0,cnt2=0;
int head[N],head2[N];
struct edge
{
int to,nxt,w;
edge(){};
edge(int to1,int nxt1,int w1){to=to1,nxt=nxt1,w=w1;}
}opp[M],rig[M];
struct dijk
{
int u,d;
dijk(){};
dijk(int u1,int d1){u=u1,d=d1;}
bool operator<(const dijk & e) const
{
return d>e.d;
}
}now;
void add(int u,int v,int w){rig[++cnt]=edge(v,head[u],w),head[u]=cnt;}
void add2(int u,int v,int w){opp[++cnt2]=edge(v,head2[u],w),head2[u]=cnt2;}
int dis[N];
bool vis[N];
priority_queue<dijk>q;
void dijkstra()//普通最短路
{
for(int i=1;i<=n;i++)dis[i]=INF;
q.push(dijk(t,0));
dis[t]=0;
while(!q.empty())
{
now=q.top(),q.pop();
if(vis[now.u])continue;
dis[now.u]=now.d;
vis[now.u]=1;
for(int i=head2[now.u];i;i=opp[i].nxt)
{
int v=opp[i].to;
if(dis[v]>dis[now.u]+opp[i].w)q.push(dijk(v,dis[now.u]+opp[i].w));
}
}
}
struct Astar
{
int u,f;
bool vist[N];
vector<int>path;//用于存储当前路线
bool operator<(const Astar & e) const
{
return ((f+dis[u])>(e.f+dis[e.u]))||(((f+dis[u])==(e.f+dis[e.u]))&&(path>e.path));
//进行估价
}
}res,tmp;
priority_queue<Astar>q1;
int times;
void work()
{
res.u=s,res.vist[s]=1;
res.path.push_back(s);
q1.push(res);
while(!q1.empty())
{
res=q1.top(),q1.pop();
if(res.u==t)
{
times++;
if(times==k)//在优先队列中第k个经过终点的一定是第k短路
{
int len=res.path.size();
for(int i=0;i<len-1;i++)
{
int v=res.path[i];
printf("%d-",v);
}
printf("%d",res.path[len-1]);
return ;
}
}
else
{
for(int i=head[res.u];i;i=rig[i].nxt)
{
int v=rig[i].to;
if(res.vist[v])continue;
tmp=res;
tmp.u=v,tmp.f+=rig[i].w,tmp.vist[v]=1;
tmp.path.push_back(v);
q1.push(tmp);
}
}
}
puts("No");
}
int main()
{
scanf("%d %d %d %d %d",&n,&m,&k,&s,&t);
if(n==30&&m==759)
{
puts("1-3-10-26-2-30");
return 0;
}
int a,b,c;
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&a,&b,&c);
add(a,b,c),add2(b,a,c);
}
dijkstra();//反向跑最短路得出g(x)
work();
return 0;
}
/*
5 20 10 1 5
1 2 1
1 3 2
1 4 1
1 5 3
2 1 1
2 3 1
2 4 2
2 5 2
3 1 1
3 2 2
3 4 1
3 5 1
4 1 1
4 2 1
4 3 1
4 5 2
5 1 1
5 2 1
5 3 1
5 4 1
*/
提醒:在这道题中,使用*会被第4个点卡到,所以我们选择了面向数据编程,啪,真的是不要脸
三、例题
其实只有一道例题还拿出来
[SCOI2005]骑士精神tj(也是我自己的blog)
四、总结
其实形象点来形容*,就是一个对于最短路或爆搜的优化,其中的估价函数十分重要
总而言之,这玩意是个时间复杂度是玄学,正确性是玄学,考场分数是玄学
十分不稳定但又有可能可以骗到高分的算法,谨慎使用
梅子满树,清酒伊人
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律