蓝桥杯 ALGO-1001 跳马

目录

题目:

问题描述

输入格式

输出格式

样例输入

样例输出

数据规模和约定

分析:

C++代码:

最后:


题目:

问题描述

  一个8×8的棋盘上有一个马初始位置为(a,b),他想跳到(c,d),问是否可以?如果可以,最少要跳几步?

输入格式

  一行四个数字a,b,c,d。

输出格式

  如果跳不到,输出-1;否则输出最少跳到的步数。

样例输入

1 1 2 3

样例输出

1

数据规模和约定

  0<a,b,c,d≤8且都是整数。

分析:

       1. 题目给了一张8*8的图。

        2.玩过象棋的朋友都知道马是可以跳到任意位置的,所以从初始位置到目标位置一定可以到达。如果你不知道,没关系,你可以自己推理一下,如果你不想自己推理,也没关系,也可以让程序帮我们做。

        3.那么每个点位会有8种跳的可能,但边角的点位有可能跳出棋盘。

        4.棋盘上有8*8=64个点位,我们求某一点按一定要求到达另一点。很明显,这是一道无权图的最短路径问题。

        5.无权图的最短路径问题我们可以使用广度优先遍历来实现。

        6.想象以初始位置结点为起始节点,跳一次可达到的结点为第一层,跳两次可达到的结点为第二层...,我们广度优先的一层层遍历,先找到目标结点,就输出层数。那么最少步数就出来啦。

        注:因为我已知棋子一定是可以跳到目标位置的,所以没有进行是否跳得到的判断(你问我怎么知道,其实我不怎么玩象棋,但我思考后发现做出这种判断用代码实现怪麻烦的,就自己画了个棋盘在上面推了推发现,所有位置都能跳到。)

这里有大佬做的漫画式讲解,完全解析  图的 “最短路径” 问题,链接在这:

https://zhuanlan.zhihu.com/p/65340385

C++代码:

#include <iostream>
#include <queue>

using namespace std;

//因为 0<a,b,c,d≤8且都是整数。
//所以创建棋盘,行列号分别为1~8

//因为网络上关于图的最短路径问题搞这个那个的算法很复杂,
//但实际这道题很简单,这是一个已知起点终点的无权无向图的最短路径问题,是最短路径里最简单的了 
//所以我们不要把问题复杂化 


//首先,建立该无向图 
//简单问题简单做,不涉及增加与删除,做个顺序结构表示就好

struct chess_position
{
	//定义结构体——棋子位置,8*8的棋盘有64个棋子位置构成 
	 
	int x;  // 行,范围1~8; 
	int y;  // 列,范围1~8;
	int next_num;  // 从该位置可以跳到几个位置 
	int next[8];  // 从该位置可以跳到位置序号 
};

struct chess_position checkerboard[64];  //定义棋盘。 

//在定义完后,紧接着的那就是初始化了。
//初始化要做什么呢?
//1.每个棋子位置都是有序号的,0~63,那么这些棋子的坐标x,y需要确定。
//2.每个棋子都要找到其下一步能跳到的位置、数量。
//因为棋盘是8*8的,所以不用担心初始化的时间复杂度问题。 

void Init_checkerboard()
{
	int j,k; j=k=1;  // 为棋盘位置赋值坐标 
	 
	for(int i=0;i<64;i++)
	{
		checkerboard[i].x=j;
		checkerboard[i].y=k;
		checkerboard[i].next_num=0;
		k++;
		if(k>8)
		{
			k=1;
			j++;
		}
	}
	
	//接着要为每个棋子找到其下下一步可以走的位置
	for(int i=0;i<64;i++)
	{
		//下一步位置有8个
		int x= checkerboard[i].x;
		int y= checkerboard[i].y;
		
		//可以自己画张纸,看一下位置。 
		int next_x[8]={x-1,x+1,x-2,x+2,x-2,x+2,x-1,x+1};
		int next_y[8]={y-2,y-2,y-1,y-1,y+1,y+1,y+2,y+2};
		
		//然后判断位置是否在棋盘内,
		//然后计算其序号,放入结构体next中,记下数量放入next_num中 
		
		for(int j=0;j<8;j++)
		{
            //判断其是否在棋盘内
			if(next_x[j]>=1&&next_x[j]<=8)
			{
				if(next_y[j]>=1&&next_y[j]<=8)
				{
					//如果我们通过下一步棋子坐标,遍历棋盘在其中找序号,那未免太蠢了
					//通过位置算出序号是比较快的。
					int next_no;
					next_no= (next_x[j]-1)*8+next_y[j]-1;
					checkerboard[i].next[checkerboard[i].next_num]=next_no;
					checkerboard[i].next_num++;
					//初始化完成。 
				}
			}
		}
	} 
	
	
}


int main() 
{
	Init_checkerboard();
	/*
	//检查一下这个结构 
	for(int i=0;i<64;i++)
	{
		printf("%d %d %d ",checkerboard[i].x,checkerboard[i].y,checkerboard[i].next_num);
		for(int j=0;j<checkerboard[i].next_num;j++)
		{
			printf("%d ",checkerboard[i].next[j]);
		}
		printf("\n");
	} 
	*/
		 
	
	int a,b,c,d;
	scanf("%d%d%d%d",&a,&b,&c,&d);
	
	//在实现了图的数据结构、输入数据后
	
	//首先将起始坐标与目的坐标转为序号。 
	int start_num=(a-1)*8+b-1;
	int aim_num=(c-1)*8+d-1;
	
	//然后我们要开始进行广度优先遍历。
	queue<int> q;  // 广度优先遍历所需的队列
	
	int res=0;  // 记录结果 
	
	q.push(start_num);
	
	int now_float_num=1;  // 当前层结点数 
	int next_float_num=0;  // 下一层层结点数
	int time=0;  //  记录已遍历结点个数,以判断当前层数。
	while (!q.empty())
	{
		if(q.front()==aim_num) 
		{
			//找到了
			break; 
		}
		else
		{
			time++;   

			//这个结点不是,得把他的子节点压入队列
			for(int i=0;i<checkerboard[q.front()].next_num;i++)
			{
                //checkerboard[q.front()].next_num是当前结点的子节点个数
				q.push(checkerboard[q.front()].next[i]);
			}
			 
			//然后下一层的结点数更新。
			next_float_num+=checkerboard[q.front()].next_num;
			
			
			//然后吐出当前遍历过的结点
			q.pop(); 
			
			//然后得判断一下当前层的结点是不是都访问完了
			if(time==now_float_num)
			{
				//若是访问完了,那么就得
				//1.更新层数
				//2.更新当前层结点数
				//3.更新下一层结点数
				//4.重置当前层遍历个数 
				res++;
				now_float_num=next_float_num;
				next_float_num=0;
				time=0;
			}
		}
	}
	printf("%d",res);
	return 0;
}

最后:

代码中的广度优先遍历与普遍性的广度优先遍历有些不同:

        1.这里在遍历时通过计数来计算遍历层数,这里遍历层数即结果

        2.没有对已经访问过的位置进行标记,因为这里不需要

        3.在输出结果时,没有考虑到找不到路径的情况,因为我们已知一定有解的。

        这样,我们就很容易的就做出了这道题的普适性解,为什么普适呢?因为无论棋盘多大,都能做出一点修改依然适用。而且最终程序性能也还不错。

        细心的小伙伴会发现,其中棋子位置数据结构中的坐标其实是可有可无的,但方便理解嘛。反正也不占多少空间。

        代码中其实有很多没必要的地方,就像把棋子位置编号,存储其下一步可能之类的。但这样有助于思考,可以让问题简单化。所以按照这样的思路,代码整体看起来好像很长,但思考难度小,容易编写,且在问题较大时(当然,这个8*8是很小的),能够通过牺牲空间来解放时间,而且代码有很多是注释。

        欢迎交流。

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值