(上面两个觉得不够通俗易懂,可以看下面的)
最后,附上一个非常的经典的题目题解,觉得蛮屌的,先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;
}