关于 A* 和 IDA* 的讨论(先讨论一下A*,剩下留着慢慢写)

本文主要探讨A*和IDA*算法,包括它们的原理和使用。通过对比,展示了A*在实际问题中的应用,如八数码难题和骑士移动问题,分别用BFS、DFS和A*进行求解。尽管A*在某些简单问题上可能效率稍低,但其搜索性能在复杂场景中通常更优。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A*算法百度百科

IDA*算法百度百科

(上面两个觉得不够通俗易懂,可以看下面的)

A*算法,点我点我!

IDA*算法,点我点我!

最后,附上一个非常的经典的题目题解,觉得蛮屌的,先mark一下,以后慢慢学习学习!

八数码的八境界,点我点我!


接下来先讨论一下A*的算法, 具体理论知识还是以上面为主,接下来先附上一道经典题目knight moves的4种算法,分别是BFS,DFS,A*,数学方法。

在POJ上,在我没用A*时提交是43ms,而A*得算法是67ms,因为题目比较简单,所以猜想用高端的方法反而降低效率。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int a,b;
int c1,c2;
char t[3],tt[3];
bool vis[10][10];
int dx[]={1,2,1,2,-1,-2,-1,-2};
int dy[]={-2,-1,2,1,2,1,-2,-1};

struct node{
	int x,y;
	int step;
};

bool judge(int x,int y){
	if(x<1||x>8||y<1||y>8)	return false;
	if(vis[x][y])	return false;
	return true;
}

queue<node>q;
void bfs(){
	memset(vis,0,sizeof(vis));
	node s,e;
	while(!q.empty())
		q.pop();
	s.x=c1,s.y=a,s.step=0;
	q.push(s);
	vis[s.x][s.y]=1;
	while(!q.empty()){
		s=q.front();
		q.pop();
		if(s.x==c2&&s.y==b){
			printf("To get from %s to %s takes %d knight moves.\n",t,tt,s.step);
			break;
		}
		for(int i=0;i<8;i++){
			e.x=s.x+dx[i];
			e.y=s.y+dy[i];
			if(judge(e.x,e.y)){
				e.step=s.step+1;
				q.push(e);
				vis[e.x][e.y]=1;
			}
		}
	}
}

int main()
{
	while(scanf("%s%s",t,tt)!=EOF)  
    {  
        c1=t[0]-'a'+1;
		a=t[1]-'0';  
        c2=tt[0]-'a'+1;
		b=tt[1]-'0';  
        bfs();  
    }  

	return 0;
}

接下来是看了网上的解题报告,然后总结的解法:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <climits>

const int MAX = 8;
const int dirx[MAX] = {-2,-2,2,2,-1,-1,1,1},diry[MAX] = {1,-1,-1,1,2,-2,-2,2};

int cstep[MAX][MAX],minx,dx,dy;

void init(){
	int i,j;

	for(i=0;i<MAX;++i){
		for(j=0;j<MAX;++j){
			cstep[i][j] = INT_MAX;
		}
	}
}

void dfs(int x,int y,int cnt){
	if(x<0 || y<0 || x>=MAX || y>=MAX)return;
	if(x==dx && y==dy){
		if(cnt<minx)minx = cnt;
		return;
	}
	//通过下面注释掉的main方法,运行处8*8棋盘,任意两个点最短距离的最大值为6
	//所以加上这个剪枝,不加这个剪枝就超时
	if(cnt>6)return;
	if(cnt>cstep[x][y])return;
	cstep[x][y] = cnt;

	int i,tx,ty;

	for(i=0;i<MAX;++i){
		tx = x + dirx[i];
		ty = y + diry[i];
		dfs(tx,ty,cnt+1);
	}

}

int main(){
	//freopen("in.txt","r",stdin);
	char csx,csy,cdx,cdy;
	int sx,sy;
	while(scanf("%c%c %c%c%*c",&csy,&csx,&cdy,&cdx)!=EOF){
		sx = csx-'1';
		sy = csy-'a';
		dx = cdx-'1';
		dy = cdy-'a';
		minx = INT_MAX;
		init();
		dfs(sx,sy,0);
		printf("To get from %c%c to %c%c takes %d knight moves.\n",csy,csx,cdy,cdx,minx);
	}
    return 0;
}



/*
int main(){
//	freopen("out.txt","w",stdout);
	int i,j,ans;
	dx = 0,dy = 0;
	ans = -1;
	for(i=0;i<8;++i){
		for(j=0;j<8;++j){
			minx = INT_MAX;
			init();
			dfs(i,j,0);
			printf("%d,",minx);
			if(minx>ans)ans = minx;
		}
	}
	printf("\n%d\n",ans);
	return 0;
}
*/


下面这个代码是A*,是上面的A*算法入门的作者给的一种解:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
 
struct knight{
    int x,y,step;
    int g,h,f;
    bool operator < (const knight & k) const{      //重载比较运算符
    return f > k.f;
    }
}k;
bool visited[8][8];                                //已访问标记(关闭列表)
int x1,y1,x2,y2,ans;                               //起点(x1,y1),终点(x2,y2),最少移动次数ans
int dirs[8][2]={{-2,-1},{-2,1},{2,-1},{2,1},{-1,-2},{-1,2},{1,-2},{1,2}};//8个移动方向
priority_queue<knight> que;                        //最小优先级队列(开启列表)
 
bool in(const knight & a){                         //判断knight是否在棋盘内
    if(a.x<0 || a.y<0 || a.x>=8 || a.y>=8)
        return false;
    return true;
}
int Heuristic(const knight &a){                    //manhattan估价函数
    return (abs(a.x-x2)+abs(a.y-y2))*10;
}
void Astar(){                                      //A*算法
    knight t,s;
    while(!que.empty()){
        t=que.top(),que.pop(),visited[t.x][t.y]=true;
        if(t.x==x2 && t.y==y2){
            ans=t.step;
            break;
        }
        for(int i=0;i<8;i++){
            s.x=t.x+dirs[i][0],s.y=t.y+dirs[i][1];
            if(in(s) && !visited[s.x][s.y]){
                s.g = t.g + 23;                 //23表示根号5乘以10再取其ceil
                s.h = Heuristic(s);
                s.f = s.g + s.h;
                s.step = t.step + 1;
                que.push(s);
            }
        }
    }
}
int main(){
    char line[5];
    while(gets(line)){
        x1=line[0]-'a',y1=line[1]-'1',x2=line[3]-'a',y2=line[4]-'1';
        memset(visited,false,sizeof(visited));
        k.x=x1,k.y=y1,k.g=k.step=0,k.h=Heuristic(k),k.f=k.g+k.h;
        while(!que.empty()) que.pop();
        que.push(k);
        Astar();
    printf("To get from %c%c to %c%c takes %d knight moves.\n",line[0],line[1],line[3],line[4],ans);
	}
    return 0;
}


接下来是数学方法,引用自一位大神:

首先,对于两个点,只用考虑其横纵坐标的差值。比如a3,a4,横坐标差值为0,纵坐标差值为1
现在设横纵坐标的差值分别是x,y
由于马只能有8种方法,实际上只会出现4种(举个例子,本来方向向量有(1,2),(2,1),(1,-2),(2,-1),(-1,2),(-2,1),(-1,-2),(-2,-1),但是如果要最小的次数,就不可能同时出现(1,2)和(-1,-2),依次类推)
 所以,我们设方向向量为(1,2),(2,1),(2,-1),(1,-2)的分别有a,b,c,d次,其中a,b,c,d可以为负数,a为负数代表方向向量为(-1,-2)
于是,可以列两个方程:
a+2b+2c+d=x
 2a+b-c-2d=y
我们要求的是|a|+|b|+|c|+|d|的最小值
 首先把a,b,看做常量,解得
c=(-4a-5b+2x+y)/3
 d=(5a+4b-x-2y)/3
那么有a+2b和2x+y模3同余
 现在2x+y已知,对于b进行枚举,由于-n/2<=b<=n/2,进行枚举,对每个知道a模3是多少,进而再对可能的a进行枚举,从而解出c,d,进而求出总步数。
 但是,特别要注意一点,就是角落的问题。
 比如a1,b2按上面方法算的是2,实际是4.
经过计算知,对于8*8的只有4种情况:a1 b2;a8 b7;g2 h1;g7 h8;
对这四种情况单独拿出来说就好了。。
 以上就是求解的数学方法。下面贴代码,对于原来的8*8题目的。
 在poj2243上AC了,228K,32MS。
n*n的稍作修改即可。
--------------------------------

#include <iostream>
 #include <string>
 using namespace std;

 int f(int a)    //就是abs(a),绝对值
{
     if (a<0)
         return 0-a;
     return a;
 }

 int main()
 {
     string s1,s2;
     int a,b,c,d,x,y,s,m;
     while (cin >> s1 >> s2)
     {
         if ((s1=="a1" && s2=="b2") || (s1=="b2" && s2=="a1") || (s1=="g2" && s2=="h1") || (s1=="h1" && s2=="g2"))
         {
             cout << "To get from " << s1 << " to " << s2 << " takes 4 knight moves." << endl;
             continue;
         }
         if ((s1=="a8" && s2=="b7") || (s1=="b7" && s2=="a8") || (s1=="g7" && s2=="h8") || (s1=="h8" && s2=="g7"))
         {
             cout << "To get from " << s1 << " to " << s2 << " takes 4 knight moves." << endl;
             continue;
         }
         x=s2[0]-s1[0];    //横坐标差值
        s=9999;
         y=s2[1]-s1[1];    //纵坐标差值
        for (b=-4;b<=4;b++)    //对b枚举for (b=-3;b<=3;b++)
         {
             m=y+2*x-2*b+30;    //a模3的余数
            m%=3;
             for (a=m-6;a<=m+6;a+=3)     //对a枚举
            {
                 c=(2*x+y-4*a-5*b)/3;    //求出c和d
                 d=(5*a+4*b-x-2*y)/3;
                 if (s>f(a)+f(b)+f(c)+f(d))
                     s=f(a)+f(b)+f(c)+f(d);     //判断是否是最小的
            }
         }
         cout << "To get from " << s1 << " to " << s2 << " takes " << s << " knight moves." << endl;
     }
     return 0;
 }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值