【笔记】学点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;
}
posted @ 2023-03-26 10:08  int_Hello_world  阅读(118)  评论(0编辑  收藏  举报