http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1091
编程任务:输入两个方格a和b,确定骑士在最短路径上从a到b移动的次数
算法分析:
1、 最容易想到就是深度优先搜索了,从起点向八个方向递归求解,计算起点到各个点的最短路径,但是深搜速度慢,如果棋盘大的话, 很容易就超时了
2、 然后就是广度优先搜索了,从起点进行8个方向的搜索,搜索到终点即结束
3、 觉得这个方法确实比较好,如果让我想估计想不到:棋盘是8*8的,在任意一个方格,骑士都可以走到棋盘上的任意其他方格,这样就有64种可能;因为有64格,所以要保存任意两格之间的最短路径的步数,需要64*64的数组。 int Knight[64][64];
建立64*64的矩阵数组与棋盘之间的对应关系。设矩阵数组的单元(i,j)(0=<i,j<=63),对应棋盘上的骑士从方格a(i/8,i%8)跳到方格b(j/8,j%8),如果只需要走一步则为1,否则为无穷:方格自身为0
设(x,y)为方格a和b之间的坐标差值,计算公式:简化代码,用abs()函数表示
x = i/8 – j/8; x = abs(i/8 – j/8);
y = i%8 – j%8; y = abs(i%8 – j%8);
在棋盘上(x,y)的相对位置有8个方向,如图阴影单元格
|
(-1,2) |
|
(1,2) |
|
(-2,1) |
|
|
|
(2,1) |
|
|
(0,0) |
|
|
(-2,-1) |
|
|
|
(2,-1) |
|
(-1,-2) |
|
(1,-2) |
|
可以看到,当x变化为1时y变化为2,或者x变化为2时y变化为1
所以就有: if(x == 1 && y == 2 || x == 2 && y == 1) Knight[i[j] = Knight[j][i] = 1;
然后采用floyd算法求出所有单元格的最短路径
深搜:
#include<string.h> #include<stdio.h> #define MAXN 9 int dir[8][2] = {-1,-2, -2,-1, -2,1, -1,2, 1,2, 2,1, 2,-1, 1,-2}; int ans[MAXN][MAXN]; void dfs(int si,int sj,int moves) { if(si<1||si>8||sj<1||sj>8||moves>=ans[si][sj]) return ; ans[si][sj] = moves; //最优解 int i; for(i=0;i<8;i++) dfs(si+dir[i][0],sj+dir[i][1],moves+1); } int main() { char str[6]; int i,j; while(gets(str)) { for(i=0;i<MAXN;i++) { for(j=0;j<MAXN;j++) ans[i][j] = 1000; } dfs(str[0]-'a'+1,str[1]-'0',0); printf("To get from %c%c to %c%c takes %d knight moves.\n",str[0],str[1],str[3],str[4],ans[str[3]-'a'+1][str[4]-'0']); } return 0; }
广搜:
#include<cstring> #include<iostream> #include<queue> #include<cstdio> using namespace std; struct Knight{ int x,y,moves; }; int dir[8][2] = {-1,-2, -2,-1, -2,1, -1,2, 1,2, 2,1, 2,-1, 1,-2}; int main() { char str[6]; int i; queue<Knight>Q; Knight start,end,from,to; while(gets(str)) { while(!Q.empty()) Q.pop(); start.x = str[0]-'a'+1; start.y = str[1]-'0'; start.moves = 0; end.x = str[3]-'a'+1; end.y = str[4]-'0'; Q.push(start); while(1) { from = Q.front(); Q.pop(); if(from.x == end.x && from.y == end.y) break; for(i=0;i<8;i++) { to.x = from.x + dir[i][0]; to.y = from.y + dir[i][1]; if(to.x < 1 || to.x > 8 || to.y < 1 || to.y > 8) continue; to.moves = from.moves + 1; Q.push(to); } } cout<<"To get from "<<str[0]<<str[1]<<" to "<<str[3]<<str[4]<<" takes "<<from.moves<<" knight moves."<<endl; } return 0; }
后来又定义个标记数组来限制方格的重复计算,时间确实有所缩短
#include<cstring> #include<iostream> #include<queue> #include<cstdio> using namespace std; struct Knight{ int x,y,moves; }; int dir[8][2] = {-1,-2, -2,-1, -2,1, -1,2, 1,2, 2,1, 2,-1, 1,-2}; int flag[9][9]; int main() { char str[6]; int i; queue<Knight>Q; Knight start,end,from,to; while(gets(str)) { while(!Q.empty()) Q.pop(); memset(flag,0,sizeof(flag)); start.x = str[0]-'a'+1; start.y = str[1]-'0'; start.moves = 0; end.x = str[3]-'a'+1; end.y = str[4]-'0'; Q.push(start); flag[start.x][start.y] = 1; while(1) { from = Q.front(); Q.pop(); if(from.x == end.x && from.y == end.y) break; for(i=0;i<8;i++) { to.x = from.x + dir[i][0]; to.y = from.y + dir[i][1]; if(to.x < 1 || to.x > 8 || to.y < 1 || to.y > 8) continue; if(!flag[to.x][to.y]) { to.moves = from.moves + 1; Q.push(to); flag[to.x][to.y] = 1; } } } cout<<"To get from "<<str[0]<<str[1]<<" to "<<str[3]<<str[4]<<" takes "<<from.moves<<" knight moves."<<endl; } return 0; }
Floyd算法:
#include <stdio.h> #include <string.h> #include <math.h> int Knight[64][64]; void work() //求出棋盘上任意两个格子之间的最短路径 { int i,j,k,x,y; for(i=0;i<64;i++) for(j=0;j<64;j++) Knight[i][j] = 30; for(i=0;i<64;i++) { for(j=0;j<64;j++) { x = abs(i/8 - j/8); //棋盘上两格之间的相对位置 y = abs(i%8 - j%8); if(x == 1 && y == 2 || x == 2 && y == 1) Knight[i][j] = Knight[j][i] = 1; //只需跳一步 } Knight[i][i] = 0; } for(k=0;k<64;k++) //Floyd算法求任意两格之间的最短路径 { for(i=0;i<64;i++) { for(j=0;j<64;j++) { if(Knight[i][k]+Knight[k][j]<Knight[i][j]) Knight[i][j]=Knight[i][k]+Knight[k][j]; } } } } int main() { work(); //保存任意两点之间的最短路径 char str[6]; int start,end; while(gets(str)) { start = (str[0]-'a')*8 + str[1] - '1'; end = (str[3]-'a')*8 + str[4] - '1'; printf("To get from %c%c to %c%c takes %d knight moves.\n",str[0],str[1],str[3],str[4],Knight[start][end]); } return 0; }