【笔记】学点A*算法
最近浅学了点A*算法的相关知识,记点笔记
前置知识:启发式搜索
定义:
A * 搜索算法(英文:A*search algorithm,A * 读作 A-star),简称 A * 算法,是一种在图形平面上,对于有多个节点的路径求出最低通过成本的算法。它属于图遍历(英文:Graph traversal)和最佳优先搜索算法(英文:Best-first search),亦是 BFS 的改进。
--OI WiKi
A* 算法,也叫A star 算法,是对bfs的一种优化。正常的bfs是每个点轮流扩展,直到扩展到答案为止。然而这样做会有很多节点的扩展是没有意义的,时间复杂度就炸了。A*算法就是对需要扩展的每个节点估计一个价值,根据价值按顺序进行搜索。
( A * 算法也属于启发式搜索,所以基本就是启发式搜索那套东西)
A* 算法加上迭代加深搜索就组成了 IDA*算法。(由bfs变成了dfs)
每次确定一个目标深度,搜到目标深度就返回。然后目标深度增加,再从头开始搜,再搜到目标深度。重复上述过程,直到得到答案。
实现:
A* 的实现 可以直接整个优先队列把估价放进去,按照排好的顺序搜。
IDA* 的实现与启发式搜索类似,都是写个估价函数,根据估价是否满足条件搜。
关于A*算法求k短路:
设起点为\(S\),终点为\(T\)
最暴力的做法:用bfs求最短路,从S点开始bfs,向四周拓展,那么第1次到达终点的路径就是最短路,第k次到达终点的路线就是k短路
考虑对上述做法进行一定的优化,每次拓展的时候,选择相对终点较近的点进行拓展。同样满足上述的:第一次到达终点的路径为最短路,第k次到达终点的路线为k短路。
A*算法
A* 算法和启发式搜索一样,其估价函数都为\(f(x)=g(x)+h(x)\)
在求k短路问题里,g(x)为起点S到当前节点X的实际距离
我们先用dijkstra或spfa等最短路求出各个点到终点的最短距离dis。将dis作为h(x)。
求得的估价函数就是当前这条路径的长度。
先把当前节点放进优先队列里,预估当前这条路线的最终长度,利用堆根据该长度排序。每次让堆顶出队,对堆顶进行拓展,拓展出来的点入队,堆顶第k次终点T时,就是求得的第k短路,此时的f(x)或者说g(x)+h(x),就是k短路的长度。
A* 求k短路板子题:
POJ 2449
都在代码里了。
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
inline int read() {
int x=0,f=0;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return f?-x:x;
}
void print(int x) {
if(x<0) putchar('-'),x=-x;
if(x>9) print(x/10);
putchar(x%10+48);
}
int tot,s,t,n,m,dis[1023131],head[1231313],Hd[1023131],cnt,Ct,x,y,z,k;
bool vis[1023313],f;
struct node{
int next,to,w;
}e[1023131],E[1023131];
void add(int u,int v,int w){ //正向边
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].w=w;
head[u]=cnt;
}
void AD(int u,int v,int w){ //反向边
E[++Ct].next=Hd[u];
E[Ct].to=v;
E[Ct].w=w;
Hd[u]=Ct;
}
void dj() { //最短路板子,记得跑反向边。
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
priority_queue<int,vector<pair<int,int > >,greater<pair<int,int> > >q;
dis[t]=0;
q.push(make_pair(0,t));
while(!q.empty()){
int now=q.top().second; q.pop();
if (vis[now]) continue;
vis[now]=1;
for (int i=Hd[now];i;i=E[i].next) {
if (dis[E[i].to]>dis[now]+E[i].w) {
dis[E[i].to]=dis[now]+E[i].w;
q.push(make_pair(dis[E[i].to],E[i].to));
}
}
}
}
void A_star(){
priority_queue<pair<int,pair<int,int> >,vector<pair<int,pair<int,int> > >,greater<pair<int,pair<int,int> > > >q;
//这个优先队列可能有点阴间。
//适应不了建议重载运算符
/*这个队列的构成是这样的:
pair<int,pair<int,int> >
第一个int为预估出来的这条路的最终长度,第二个int为f(x), 第三个int为节点编号
*/
q.push(make_pair(dis[s],make_pair(0,s)));
while(!q.empty()) {
int now=q.top().second.second;
//now 代表当前节点编号。
int val=q.top().second.first;
//val其实就是f(x),val=g(x)+h(x)
//g(x)就是从起点到当前节点所走的距离。
//h(x)就是dis[now],及当前节点到终点的最短距离。
q.pop();
if (now==t) {
tot++;
if (tot==k) {
cout<<val;
f=1;
return ;
}
}
for (int i=head[now];i;i=e[i].next) {
q.push(make_pair(e[i].w+dis[e[i].to]+val,make_pair(e[i].w+val,e[i].to)));
}
}
}
signed main(){
n=read(); m=read();
for (int i=1;i<=m;++i) {
x=read(); y=read(); z=read();
add(x,y,z);
AD(y,x,z);
//建反向边从终点跑最短路,求出来的就是所有点到终点的距离。
}
s=read(); t=read(); k=read();
if (s==t) k++;//这题比较特殊,起点和终点相等时k不能等于1
dj();
A_star();
if (!f) {
cout<<-1;
}
return 0;
}
IDA* 例题:
[SCOI2005]骑士精神
题目描述
输入格式
第一行有一个正整数 \(T\)(\(T \le 10\)),表示一共有 \(T\) 组数据。
接下来有 \(T\) 个 \(5 \times 5\) 的矩阵,0
表示白色骑士,1
表示黑色骑士,*
表示空位。两组数据之间没有空行。
输出格式
对于每组数据都输出一行。如果能在 \(15\) 步以内(包括 \(15\) 步)到达目标状态,则输出步数,否则输出 -1
。
样例 #1
样例输入 #1
2
10110
01*11
10111
01001
00000
01011
110*1
01110
01010
00100
样例输出 #1
7
-1
提示
思路:
不难发现,挪动棋子和挪动空位效果是一样的,由于棋子太多不好处理,所以我们选择挪动空位。
将当前状态和目标状态不匹配的点的个数作为启发式信息,构建估价函数。
因为是让我们15步以内就收手,所以可以加个迭代加深作为优化(IDA*)
比较板子的一个题,直接上代码吧:
#include<bits/stdc++.h>
using namespace std;
char ch;
bool flag;
int T,map1[11][11],n,m,ans,ax,ay,xx,yy;
int dx[]={0,1,1,2,2,-1,-1,-2,-2};
int dy[]={0,2,-2,1,-1,2,-2,1,-1};
int Map[6][6]={ //目标状态
{0,0,0,0,0,0},
{0,1,1,1,1,1},
{0,0,1,1,1,1},
{0,0,0,2,1,1},
{0,0,0,0,0,1},
{0,0,0,0,0,0}
};
int h() { //估价函数
int tot=0;
for (int i=1;i<=5;++i) {
for (int j=1;j<=5;++j) {
if (map1[i][j]!=Map[i][j]) tot++;
}
}
return tot;
}
void A_star(int now,int x,int y,int T) {
//now为当前走到了第几步,x,y为空位的坐标,T为目标深度(迭代加深)
if (now==T) {
if (!h()) flag=1;
return ;
}
for (int i=1;i<=8;++i) {
int xx=x+dx[i]; int yy=y+dy[i];
if (xx>5||yy>5||xx<1|yy<1) continue;
swap(map1[x][y],map1[xx][yy]);
if (h()+now<=T) A_star(now+1,xx,yy,T);
swap(map1[x][y],map1[xx][yy]); //回溯
}
}
signed main(){
n=5;
cin>>T;
while(T--) {
for (int i=1;i<=n;++i) {
for (int j=1;j<=n;++j) {
cin>>ch;
if (ch=='*') {
ax=i,ay=j;
map1[i][j]=2;
}
else if (ch=='0') map1[i][j]=0;
else map1[i][j]=1;
}
}
if (!h()) {
cout<<0<<"\n";
continue;
}
flag=0;
for (int j=1;j<=15;++j) { //迭代加深
A_star(0,ax,ay,j);
if (flag) {
cout<<j<<"\n";
break;
}
}
if(!flag) {
cout<<-1<<"\n";
}
}
return 0;
}