引入
A*算法主要思想是尽可能减少搜索空间;
我觉得A*算法和迪杰斯特拉算法真的很像;
具体看下面;
一般思路
将BFS中的队列换成优先队列,这里我们使用小根堆来实现;
队列中存放的值为起点到当前点的真实距离与当前点到终点的估计距离;
这个估计距离是由我们写的一个估价函数来算的;
为什么说A*和迪杰斯特拉很像;
因为迪杰斯特拉不就是把估计距离当作零吗?
保证正确性
设 g ( i ) g(i) g(i)表示点 i i i到终点的真实距离;
f ( i ) f(i) f(i)表示点 i i i到终点的估计距离;
只要保证 f ( i ) ≤ g ( i ) f(i)≤g(i) f(i)≤g(i)且估计距离为非负数,那么第一次从队头出来的答案必然是正确的;
当然越接近越好,如果预估到0,那不就是个迪杰斯特拉…
应用场景
需要保证答案有解;
因为如果无解,那么我们会搜索完全部的状态;
那就退化成了普通的BFS;
但是我们又使用了优先队列( O ( l o g n ) O(logn) O(logn));
因此比普通的BFS(普通队列 O ( 1 ) ) O(1)) O(1))效率还低;
当然,如果无法判断是否有解;
我觉得可以试试A*,毕竟一般来说效率都比朴素的高;
注意点
A*算法只能保证当终点出队的时候距离是最小的;
但是不能保证中间某些点出队的时候距离最小;
因此每个点不止被访问一次(因为有可能先遍历到错的路径)
普通BFS、A*、迪杰斯特拉判重对比
普通BFS:一般在入队的时候判重;
堆优化的迪杰斯特拉:一般在出队的时候判重;
A*:类似spfa,不能判重;
例题
八数码
思路
我们发现方块左右移动是不改变逆序对的数量;
而上下移动改变逆序对的数量必然是偶数(可能是0);
因此如果初始状态逆序对的数量是奇数,那么必然无解;
如果是偶数,那么必然有解;
因为最终排列是 12345678 x 12345678x 12345678x;
而我们只需要保证,预估距离小于等于真实距离;
而每个点最终必然是按 12345678 x 12345678x 12345678x排列;
因此我们的估价函数就认为是当前状态到最终状态的曼哈顿距离;
曼哈顿距离如下;
Code
#include <iostream>
#include <cstdio>
#include <string>
#include <utility>
#include <queue>
#include <algorithm>
#include <unordered_map>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
//估价函数
int f(string state){
int ret = 0;
for(int i=0;i<state.size();++i){
if(state[i] != 'x'){
//计算曼哈顿距离
int tar = state[i] - '1';
int nowx = i/3,nowy = i%3,tarx = tar/3,tary = tar%3;
ret += abs(nowx-tarx) + abs(nowy-tary);
}
}
return ret;
}
string over = "12345678x";
int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};//右左下上
string opt = "rldu";
typedef pair<int,string> pis;
priority_queue<pis,vector<pis>,greater<pis> >pq;
unordered_map<string,int> dist;//真实距离
unordered_map<string,pair<string,char> > pre;
string bfs(string start){
if(start == over) return "";
dist[start] = 0;
pq.push({f(start),start});
while(!pq.empty()){
auto u = pq.top();
pq.pop();
string now = u.second;
if(now == over) break;
//先找到x
int px,py;
for(int i=0;i<now.size();++i){
if(now[i] == 'x'){
px = i/3,py = i%3;
break;
}
}
//将x上下左右移动
for(int i=0;i<4;++i){
string state = now;
int xx = px + dx[i],yy = py + dy[i];
if(xx < 0 || xx >= 3 || yy < 0 || yy >= 3){
continue;
}
//移动过去
swap(state[px*3+py],state[xx*3+yy]);
if(!dist.count(state) || dist[state] > dist[now] + 1){
dist[state] = dist[now] + 1;
pre[state] = {now,opt[i]};
//真实距离 + 预测距离
pq.push({f(state)+dist[state],state});
}
}
}
string ret;
while(over != start){
ret += pre[over].second;
over = pre[over].first;
}
reverse(ret.begin(),ret.end());
return ret;
}
void solve(){
string start,seq;//seq用于计算逆序对
char ch;
while(cin >> ch){
start += ch;
if(ch != 'x') seq += ch;
}
int num = 0;
for(int i=1;i<8;++i){
for(int j=i-1;j>=0;--j){
if(seq[i] > seq[j]) ++num;
}
}
if(num & 1) cout << "unsolvable\n";
else cout << bfs(start) << '\n';
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
solve();
return 0;
}
第K短路
思路
因为A*算法能保证终点出队的时候必然是最优的;
那么当终点第 k k k次出队的时候,这条路必然就是第 k k k短路
接着是考虑估价函数;
对于估价函数来说,我们只需要保证预估距离小于等于真实距离;
那么我们只需要求终点到所有点的最短路即可;
也就是用一个迪杰斯特拉求单源最短路;
以最短路作为我们的估价函数,那必然是符合要求的;
因此我们只需要
- 由终点跑迪杰斯特拉单源最短路;
- 从起点跑A*算法求第 k k k短路;
注意
因此当起点和终点重合的时候,我们需要找第
k
+
1
k+1
k+1短路;
因为最短路一条边也不包含;
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
struct Edge{
int to,next,val;
}e[N<<1];//需要正向边和反向边
int head[N],dist[N],vis[N],rhead[N],cnt;
void add(int head[],int u,int v,int w){
e[++cnt].to = v;
e[cnt].val = w;
e[cnt].next = head[u];
head[u] = cnt;
}
int n,m,start,over,k;
typedef pair<int,int> pii;//真实距离,编号
typedef pair<int,pii> pip;//真实距离+预估距离,真实距离,编号
void dijkstra(){
memset(dist,0x3f,sizeof dist);
priority_queue<pii,vector<pii>,greater<pii>> heap;
dist[over] = 0;
heap.push({0,over});
while(!heap.empty()){
auto u = heap.top();
heap.pop();
if(vis[u.second]) continue;
vis[u.second] = 1;
for(int i=rhead[u.second];i;i=e[i].next){
int to = e[i].to;
if(dist[to] > dist[u.second] + e[i].val){
dist[to] = dist[u.second] + e[i].val;
heap.push({dist[to],to});
}
}
}
}
int f(int x){//估价函数
return dist[x];
}
int a_star(){
//判断无解的情况
//也就是如果起点无法到终点
//比如数据
//3 2
//1 2 1
//2 1 1
//1 3 1000 跑满k,但是不存在,硬跑TLE
if(dist[start] == 0x3f3f3f3f) return -1;
priority_queue<pip,vector<pip>,greater<pip>> heap;
heap.push({f(start),{0,start}});
int times = 0;
while(!heap.empty()){
auto u = heap.top();
heap.pop();
if(u.second.second == over) ++times;//记录第几次到终点
//返回真实距离
if(times == k) return u.second.first;
for(int i=head[u.second.second];i;i=e[i].next){
int to = e[i].to;
//边权 + 当前点已经包含的距离
int real_dist = e[i].val+u.second.first;
heap.push({real_dist+f(to),{real_dist,to}});
}
}
return -1;
}
void solve(){
cin >> n >> m;
for(int i=1,u,v,w;i<=m;++i){
cin >> u >> v >> w;
add(head,u,v,w);
add(rhead,v,u,w);
}
cin >> start >> over >> k;
if(start == over) ++k;//第k+1短路
dijkstra();
cout << a_star() << '\n';
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
solve();
return 0;
}