A*算法
用途#
- 用于最小步数模型,可以在庞大的搜索空间中减少搜索范围;
- 需要注意的是,如果没有路径可以到达终点,那么A*算法还是会搜索所有的状态.这种情况下,A*算法每选择一个状态的时间复杂度为
O(logn)
,而普通BFS只需要O(1)
- 其次,A*算法一般应用于非负权边图;如果含负权边且不存在负环,则也可以应用(待定)
原理:#
- A*算法的核心在于估价函数,用于估算当前状态到终点的步数;
- 假设实际距离为
g(state)
,估计距离为f(state)
,则估价函数必须满足:f(state) <= g(state)
; - 越接近则算法效果越好,如果估计值全部为0,则退化为dijkstra算法
- A*算法使用小根堆,按照
d(u) + f(u)
排序 (假设到达状态u已用步数为d(u))
性质#
1.当终点状态第一次出队,即表示找到了最小步数
证明-反证法#
- 假设不是最小步数,即有:
d(end) > d(优)
- 说明堆中尚存在一条最优路径上的点u满足
d(优) = d(u) + g(u) >= d(u) + f(u)
(堆中至少包含起点) - ->
d(end) > d(优) >= d(u) + f(u)
, 这与小根堆的性质产生矛盾,说明假设错误
2.除终点外,每一点可能被更新多次;
3.A*算法不能保证到每一点(除终点外)在入队/出队时距离是最优的,这一点与dijkstra算法区分
算法步骤#
while(q.size())
{
优先队列中堆顶元素出队;
if(堆顶元素==终点状态) break;
使用堆顶元素更新其他状态,对更新的状态进行估价,并将其入队
}
例题 -八数码#
题解#
- 估计函数:计算当前状态下每一数字距离其正确位置的曼哈顿距离之和
- 八数码问题是否有解:计算对应字符串中逆序对的个数,如果为偶数则有解,反之无解。大概的解释是:每一次变化对于逆序对的影响必定是偶数个,且正解中逆序对的个数也是偶数
代码#
#include <iostream>
#include <cstring>
#include <queue>
#include <unordered_map>
#include <algorithm>
using namespace std;
typedef pair<int , string> PIS;
string start;
int dx[] = {-1 , 1 , 0 , 0} , dy[] = {0 , 0 , -1 , 1};
//估价函数
int f(string str)
{
int res = 0;
for(int i = 0; i < 9; i ++)
{
if(str[i] !='x')
{
int t = str[i] - '1';
res += abs(i/3 - t/3) + abs(i%3 - t%3);
}
}
return res;
}
string bfs()
{
string over = "12345678x";
string op = "udlr";
priority_queue<PIS , vector<PIS> , greater<PIS> > heap;
unordered_map<string , int> dist;
unordered_map<string , pair<char,string>> prev;
heap.push({f(start) , start});
dist[start] = 0;
while(heap.size())
{
PIS t = heap.top();
heap.pop();
string state = t.second;
if(state == over) break;
int d = dist[state];
int u = state.find('x');
int a = u / 3 , b = u % 3;
for(int i = 0; i < 4; i++)
{
int x = a + dx[i] , y = b + dy[i];
if(x < 0 || x >= 3 || y < 0 || y >= 3) continue;
swap(state[u] , state[x*3 + y]);
if(!dist.count(state) || dist[state] > d + 1)
{
dist[state] = d + 1;
prev[state] = {op[i] , t.second};
heap.push({f(state) + dist[state] , state});
}
swap(state[u] , state[x*3 + y]);
}
}
string res;
//对状态转移进行回溯,记录操作(udlr)
while(over != start)
{
res += prev[over].first;
over = prev[over].second;
}
reverse(res.begin() , res.end());
return res;
}
int main()
{
string t;
for(int i = 0; i < 9; i++)
{
char a; cin >> a;
start += a;
if(a != 'x') t += a;
}
int cnt = 0;
for(int i = 0; i < 8; i++)
for(int j = i + 1; j < 8; j++)
if(t[i] > t[j]) cnt++;
if(cnt & 1) puts("unsolvable");
else cout << bfs() << endl;
return 0;
}
例2-第k短路#
题解#
1.终点第几次出队,即表示第几短路,证明与最短路类似
2.估价函数:反向建边,跑一遍dijkstra算法,即可以求出与真实值相同的估计值
代码#
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef pair<int,PII> PIII;
const int N = 1010 , M = 200010;
int e[M] , ne[M] , w[M] , sh[N] , eh[N] , idx;
int dist[N] , cnt[N] , n , m;
bool st[N];
int S , T , K;
void add(int l ,int r , int v , int h[])
{
e[idx] = r , w[idx] = v , ne[idx] = h[l] , h[l] = idx ++;
}
void dijkstra()
{
memset(dist , 0x3f , sizeof dist);
dist[T] = 0;
priority_queue<PII , vector<PII> , greater<PII> > heap;
heap.push({0 , T});
while(heap.size())
{
PII t = heap.top();
heap.pop();
int d = t.x , u = t.y;
if(st[u]) continue;
for(int i = eh[u]; ~i; i = ne[i])
{
int v = e[i];
if(dist[v] > dist[u] + w[i])
{
dist[v] = dist[u] + w[i];
heap.push({dist[v] , v});
}
}
st[u] = true;
}
}
int astar()
{
priority_queue<PIII , vector<PIII> , greater<PIII> > heap;
heap.push({dist[S] , {0 , S}});
while(heap.size())
{
PIII t = heap.top();
heap.pop();
int d = t.y.x , u = t.y.y;
cnt[u]++;
if(cnt[T] == K) return d;
for(int i = sh[u]; ~i; i = ne[i])
{
int v = e[i];
int tmp = d + w[i];
if(cnt[v] < K) //对于在极端情况下可能会有问题,这里是对正确性和时间做了下权衡。
heap.push({tmp + dist[v] , {tmp , v}});
}
}
return -1;
}
int main()
{
cin >> n >> m;
memset(sh , -1, sizeof sh);
memset(eh , -1, sizeof eh);
for(int i = 0; i < m; i++)
{
int l , r , v;
cin >> l >> r >> v;
add(l ,r , v , sh) , add(r , l , v , eh);
}
cin >> S >> T >> K;
if(S == T) K++; //注意特殊数据:S==T,则 K++剔除自己走到自己的情况
dijkstra();
printf("%d\n" , astar());
return 0;
}
参考资料#
Acwing-算法提高课-搜索章节:
https://www.acwing.com/activity/content/introduction/16/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构