A*与IDA*算法学习笔记

A*

简介

A*搜索算法(英文:A*search algorithm,A*读作 A-star),简称 A*算法,是一种在图形平面上,对于有多个节点的路径求出最低通过成本的算法。它属于图遍历(英文:Graph traversal)和最佳优先搜索算法(英文:Best-first search),亦是 BFS 的改进。

定义起点 s s s ,终点 t t t ,从起点(初始状态)开始的距离函数 g ( x ) g(x) g(x),到终点 (最终状态)的距离函数 h ( x ) h(x) h(x) h ′ ( x ) h'(x) h(x) ,以及每个点的估价函数 f ( x ) = g ( x ) + h ( x ) f(x)=g(x)+h(x) f(x)=g(x)+h(x)

A*算法每次从优先队列中取出一个 f f f 最小的元素,然后更新相邻的状态。

如果 h ≤ h ′ h \leq h' hh ,则 A*算法能找到最优解。

上述条件下,如果 h h h 满足三角形不等式,则 A*算法不会将重复结点加入队列。且 h h h 越大,效果越好,当然,如果 h h h 过大的话可能会直接跳过最优解。

h = 0 h=0 h=0 时,A*算法变为 DFS ;当 h = 0 h=0 h=0 并且边权为 1 1 1 时变为 BFS。

典型例题(15数码)

15 puzzle

思路分析

首先通过分析我们可以知道,使用 b f s bfs bfs 或者是普通的 d f s dfs dfs 剪枝肯定会TLE,所以考虑A*,我们先把普通的状态搜索 b f s bfs bfs 的板子打出来。这里定义两类结构体 M a t r i x Matrix Matrix S t a t e State State M a t r i x Matrix Matrix 结构体是普通 b f s bfs bfs 板子中的状态量结点,这里我们给它增加一个权值 e s t est est,表示一个预估量,预估的是从当前结点到目标状态所需的最小步数。 S t a t e State State 结构体总共有两个成员: M a t r i x Matrix Matrix e s t est est ;它的作用是在出队入队操作时作为一个标准。因为 A*的剪枝策略实际上时对 起点到当前位置的成本+当前位置到目标状态的推测值 的最小值进行优先选择,所以原先 b f s bfs bfs 中的队列要改成堆式结构,也就是优先队列,这里就是对 S t a t e State State 进行操作了。具体实现见下方代码。

Codes (A*)

#include<bits/stdc++.h>
using namespace std;
typedef struct Matrix{   //存16宫格的棋型
	    int x,y; //0所在的坐标
        int cost; //状态迭代过程中的实际花费
	    int f[17];  //将二维宫格用一维序列存储
	    int est;  //表示此棋型号还需至少est步才能到达目标状态
	    bool operator < (const Matrix &k) const{
	    	 for (int i=1; i<=16; i++)
	    	     if (f[i]==k.f[i]) continue;
	    	     else
	    	        return f[i]>k.f[i];
	    	  return false;
		}
};

typedef struct State{    //搜索过程中的状态结点
	    Matrix u;  //该状态16宫格的具体内容
	    int est;  //预计该状态总共至少还剩est步才能到达目标
	    bool operator < (const State &s) const{
		     return est>s.est;
		}
};

int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};

int main(){
    priority_queue<State> q;
    map<Matrix,bool> vis;  //表示每个棋型状态是否到达过
    int x,cnt=0,cost_sum=0;
    int estimate[25][25][25]={0}; //estimate[i][j][k]表示从位置(i,j)到目标答案中k所在位置的曼哈顿距离
    
    for (int i=0; i<=15; i++){
   	   for (int j=1; j<=16; j++){
   	       if (i==0){
   	          if (j%4!=0){
   	             estimate[(j-1)/4+1][j%4][0]=abs((j-1)/4+1-4)+abs(j%4-4);	
			  }
			  else{
			  	 estimate[j/4][4][0]=abs(j/4-4)+abs(4-4);
			  }	
		   }
		   else{
		   	  if (j%4!=0){
		   	  	 if (i%4!=0){
		   	  	    estimate[(j-1)/4+1][j%4][i]=abs((j-1)/4+1-(i-1)/4-1)+abs((j%4-i%4));	
				  }
				  else{
				  	 estimate[(j-1)/4+1][j%4][i]=abs((j-1)/4+1-i/4)+abs(j%4-4);
				  }
			  }
			  else{
			  	 if (i%4!=0){
		   	  	    estimate[j/4][4][i]=abs(j/4-(i-1)/4-1)+abs(4-i%4);	
				  }
				  else{
				  	 estimate[j/4][4][i]=abs(j/4-i/4)+abs(4-4);
				  }
			  }
		   }	
	   }
   }
   
	Matrix uu;       
	for (int i=1; i<=4; i++)
	   for (int j=1; j<=4; j++){
	   	   scanf("%d",&x);  //读入初始的每个棋盘位置上的值
	   	   if (x==0)
	   	      uu.x=i,uu.y=j;
	   	   uu.f[++cnt]=x;
	   }

	for (int i=1; i<=16; i++){  //统计初始状态所有需要的est值
		if (i%4!=0){
			cost_sum+=estimate[(i-1)/4+1][i%4][uu.f[i]];
		}
		else{
			cost_sum+=estimate[i/4][4][uu.f[i]];
		}
	}
    State start; //定义初始状态start
	start.est=uu.est=cost_sum;
	start.u=uu;
	q.push(start);
	vis[start.u]=1; start.u.cost=0;
	while (!q.empty()){   //用bfs进行搜索
		  State u=q.top(); q.pop();
		  Matrix uu=u.u,u1;
		  if (uu.est==0){ //如果预计还需0步(也就是已经到了目标状态了),输出实际花费,退出。
		  	 cout<<uu.cost<<endl;
		  	 return 0;
		  }
		  vis[uu]=1; //标记状态uu已经访问过
		  int x1,y1;
		  for (int i=0; i<4; i++){
		  	  u1=uu; //建立新状态u1
		  	  x1=uu.x+dx[i]; y1=uu.y+dy[i];
		  	  if (x1>=1 && x1<=4 && y1>=1 && y1<=4){ //如果(x1,y1)可以移动到0所在位置
		  	  	 /*更新u1状态的总est*/
                 u1.est+=estimate[x1][y1][0]-estimate[uu.x][uu.y][0]+estimate[uu.x][uu.y][uu.f[(x1-1)*4+y1]]-estimate[x1][y1][uu.f[(x1-1)*4+y1]];
				 int tmp;
				 tmp=u1.f[(uu.x-1)*4+uu.y];
				 u1.f[(uu.x-1)*4+uu.y]=u1.f[(x1-1)*4+y1];
				 u1.f[(x1-1)*4+y1]=tmp;
				 //=uu.f[(x1-1)*4+y1];
                 u1.x=x1; u1.y=y1;
				 if (vis[u1]!=1){
				 	u1.cost++;
				 	State news;
				 	news.est=u1.est+u1.cost;
				 	news.u=u1;
				 	q.push(news);
				 }
			  }
		  }
	}
	return 0;
}

但实际上,A*的实际效果并不是很好(虽然相对于普通的 b f s bfs bfs 它已经很强了),它只能过22/28个点。最后的六个点全部MLE了(不过A*能很好地处理八数码难题),所以我们考虑另一种方法来处理空间消耗过度的问题,那就是IDA*(迭代加深搜索)

IDA*

简介

IDA*,即采用迭代加深的 A*算法。相对于 A*算法,由于 IDA*改成了深度优先的方式,所以 IDA*更实用:
1.不需要判重,不需要排序;
2.空间需求减少。

迭代加深搜索

迭代加深是一种 每次限制搜索深度的深度优先搜索

它的本质还是深度优先搜索,只不过在搜索的同时带上了一个深度 d d d ,当 d d d 达到设定的深度时就返回,一般用于找最优解。如果一次搜索没有找到合法的解,就让设定的深度加一,重新从根开始。

既然是为了找最优解,为什么不用 BFS 呢?我们知道 BFS 的基础是一个队列,队列的空间复杂度很大,当状态比较多或者单个状态比较大时,使用队列的 BFS 就显出了劣势。事实上,迭代加深就类似于用 DFS 方式实现的 BFS,它的空间复杂度相对较小。

当搜索树的分支比较多时,每增加一层的搜索复杂度会出现指数级爆炸式增长,这时前面重复进行的部分所带来的复杂度几乎可以忽略,这也就是为什么迭代加深是可以近似看成 BFS 的。

具体步骤

首先设定一个较小的深度作为全局变量,进行 DFS。每进入一次 DFS,将当前深度加一,当发现 d d d 大于设定的深度 l i m i t limit limit 就返回。如果在搜索的途中发现了答案就可以回溯,同时在回溯的过程中可以记录路径。如果没有发现答案,就返回到函数入口,增加设定深度,继续搜索。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值