用队列解决骑士移动问题

一 问题描述

计算骑士从一个位置移动到另外一个位置所需的最少移动次数。骑士移动规则如下图。

输入:测试用例包含3行。

第1行:表示棋盘的长度 L,范围在 [4,300],棋盘的大小为 L * L。

第2行:骑士在棋盘的开始位置。

第3行:骑士在棋盘的结束位置。

输出:输出骑士从起点移动到终点所需的最少移动次数。如果起点和终点相等,则移动次数为零。

下面是三组测试用例的结果。

输入

输出

8

(0,0)

(7,0)

5

100

(0,0)

(30,50)

28

10

(1,1)

(1,1)

0

二 算法设计

本问题求解棋盘从起点到终点最短距离问题可以使用队列进行广度优先搜索,步骤如下:

1 如果起点正好等于终点,则返回 0。

2 将起点放入队列。

3 如果队列不为空,则队头出队,否则扩展8个方向,如果找到目标,则立即返回步数加1,否则判断是否越界;如果没有越界,则将步数加1,并放入队列,标记棋盘已访问。如果骑士的当前位置为(x,y),则移动时,当前位置坐标加上偏移量即可。例如骑士从当前位置移到右上角的位置(x-2,y+1),如下图所示。

8个方向的位置偏移如下。

x 方向:{-2,-2,-1,-1,1,1,2,2}

y 方向:{1,-1,2,-2,2,-2,1,-1}

三 实现

package concurrent;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

/**
* @className: KnightMove
* @description: 骑士移动问题
* @date: 2022/3/5
* @author: cakin
*/
public class KnightMove {
    public static void main(String[] args) {
        int dx[] = {-2, -2, -1, -1, 1, 1, 2, 2};
        int dy[] = {1, -1, 2, -2, 2, -2, 1, -1};
        Scanner input = new Scanner(System.in);
        // 棋盘的大小
        int lengh = input.nextInt();
        // 标记棋盘位置是否访问过
        boolean[][] visitFlag = new boolean[lengh][lengh];
        // 开始位置
        int sx = input.nextInt();
        int sy = input.nextInt();
        // 结束位置
        int ex = input.nextInt();
        int ey = input.nextInt();
        // 中间位置
        int tx;
        int ty;
        if (sx == ex && sy == ey) {
            System.out.println("移到步数为0");
            return;
        }
        // 定义一个队列
        Queue<Point> queue = new LinkedList<>();
        // 开始位置
        Point start = new Point();
        start.x = sx;
        start.y = sy;
        start.step = 0;
        // 进入队列
        queue.offer(start);
        int step;
        int x;
        int y;
        while (!queue.isEmpty()) {
            // 取队列头元素,同时把这个元素弹出
            start = queue.poll();
            x = start.x;
            y = start.y;
            step = start.step;
            for (int i = 0; i < 8; i++) {
                tx = x + dx[i];
                ty = y + dy[i];
                if (tx == ex && ty == ey) {
                    System.out.println("移到步数为" + (step + 1));
                    return;
                }
                if (tx >= 0 && tx < lengh && ty >= 0 && ty < lengh && !visitFlag[tx][ty]) {
                    // 中间位置
                    Point node = new Point();
                    node.x = tx;
                    node.y = ty;
                    node.step = step + 1;
                    // 满足条件入队
                    queue.offer(node);
                    visitFlag[tx][ty] = true;
                }


            }
        }
    }
}

/**
* @className: Point
* @description: 描述棋盘上的一个点
* @date: 2022/3/5
* @author: cakin
*/
class Point {
    // x 坐标
    public int x;
    // y 坐标
    public int y;
    // 步数
    public int step;
}

四 测试

绿色为输入,白色为输出

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
问题描述:将马随机放在国际象棋的 8X8 棋盘中的某个方格中,马按走棋规则进行移动。要求每个方格上只进入一次,走遍棋盘上全部 64 个方格。编制递归程序,求出马的行走路线 ,并按求出的行走路线,将数字 1,2,…,64 依次填入 8X8 的方阵输出之。测试数据:由读者指定可自行指定一个马的初始位置。实现提示:每次在多个可走位置中选择一个进行试探,其余未曾试探过的可走位置必须用适当结构妥善管理,以备试探失败时的“回溯”悔棋使用。并探讨每次选择位置的“最佳策略”,以减少回溯的次数。 背景介绍: 国际象棋为许多令人着迷的娱乐提供了固定的框架,而这些框架常独立于游戏本身。其中的许多框架都基于骑士奇异的L型移动规则。一个经典的例子是骑士漫游问题。从十八世纪初开始,这个问题就引起了数学家和解密爱好者的注意。简单地说,这个问题要求从棋盘上任一个方格开始按规则移动骑士,使之成功的游历国际象棋棋盘的64个方格,且每个方格都接触且仅接触一次。 可以用一种简便的方法表示问题的一个解,即将数字1,……,64按骑士到达的顺序依次放入棋盘的方格中。 一种非常巧妙的解决骑士漫游地方法由J.C.Warnsdorff于1823年给出。他给出的规则是:骑士总是移向那些具有最少出口数且尚未到达的方格之一。其中出口数是指通向尚未到达方格的出口数量。在进一步的阅读之前,你可以尝试利用Warnsdorff规则手工构造出该问题的一个解。 实习任务: 编写一个程序来获得马踏棋盘即骑士漫游问题的一个解。 您的程序需要达到下面的要求: 棋盘的规模是8*8; 对于任意给定的初始化位置进行试验,得到漫游问题的解; 对每次实验,按照棋盘矩阵的方式,打印每个格被行径的顺序编号。 技术提示: 解决这类问题的关键是考虑数据在计算机中的存储表示。可能最自然的表示方法就是把棋盘存储在一个8*8的二维数组board中。以(x,y)为起点时骑士可能进行的八种移动。一般来说,位于(x,y)的骑士可能移动到以下方格之一:(x-2,y+1)、(x-1,y+2)、(x+1,y+2)、(x+2,y+1)、(x+2,y-1)、(x+1,y-2)、(x-1,y-2)、(x-2,y-1)。但请注意,如果(x,y)的位置离某一条边较近,有些可能的移动就会把骑士移到棋盘之外,而这当然是不允许的。骑士的八种可能的移动可以用一个数组MoveOffset方便地表示出来: MoveOffset[0]=(-2,1) MoveOffset[1]=(-1,2) MoveOffset[2]=(1,2) MoveOffset[3]=(2,1) MoveOffset[4]=(2,-1) MoveOffset[5]=(1,-2) MoveOffset[6]=(-1,-2) MoveOffset[7]=(-2,-1) 于是,位于(x,y)的骑士可以移动到(x+MoveOffset[k].x, y+MoveOffset[k].y),其中k是0到7之间的某个整数值,并且新方格必须仍位于棋盘上。
骑士游历问题是指在一个 n x n 的棋盘上,给定一个起始位置和一个终点位置,求出骑士从起始位置出发到达终点位置所经过的最短路径。 队列可以用来实现广度优先搜索算法,解决骑士游历问题。具体实现可以参考以下Java代码: ```java import java.util.*; public class KnightTour { private static final int SIZE = 8; // 棋盘大小 private static final int[][] DIRECTIONS = {{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {2, -1}, {2, 1}}; // 骑士可以走的8个方向 public static void main(String[] args) { int[][] board = new int[SIZE][SIZE]; // 棋盘 int startX = 0; // 起始位置的x坐标 int startY = 0; // 起始位置的y坐标 int endX = 7; // 终点位置的x坐标 int endY = 7; // 终点位置的y坐标 int stepCount = bfs(board, startX, startY, endX, endY); // 使用广度优先搜索算法求解最短路径 System.out.println("骑士从 (" + startX + "," + startY + ") 到 (" + endX + "," + endY + ") 的最短路径为:" + stepCount + " 步"); } /** * 使用广度优先搜索算法求解骑士从起始位置到达终点位置所经过的最短路径 * * @param board 棋盘 * @param startX 起始位置的x坐标 * @param startY 起始位置的y坐标 * @param endX 终点位置的x坐标 * @param endY 终点位置的y坐标 * @return 骑士从起始位置到达终点位置所经过的最短路径 */ private static int bfs(int[][] board, int startX, int startY, int endX, int endY) { Queue<int[]> queue = new LinkedList<>(); queue.offer(new int[]{startX, startY}); // 将起始位置加入队列 board[startX][startY] = 1; // 标记起始位置已经访问过 while (!queue.isEmpty()) { int[] curr = queue.poll(); int currX = curr[0]; // 当前位置的x坐标 int currY = curr[1]; // 当前位置的y坐标 if (currX == endX && currY == endY) { // 如果当前位置是终点位置,则返回当前位置的步数 return board[currX][currY] - 1; } for (int[] direction : DIRECTIONS) { int nextX = currX + direction[0]; // 下一个位置的x坐标 int nextY = currY + direction[1]; // 下一个位置的y坐标 if (nextX >= 0 && nextX < SIZE && nextY >= 0 && nextY < SIZE && board[nextX][nextY] == 0) { // 如果下一个位置在棋盘范围内且未访问过 board[nextX][nextY] = board[currX][currY] + 1; // 标记下一个位置已经访问过,并记录步数 queue.offer(new int[]{nextX, nextY}); // 将下一个位置加入队列 } } } return -1; // 如果无法到达终点位置,则返回-1 } } ``` 在上面的代码中,我们使用一个二维数组 `board` 来表示棋盘,其中 `board[i][j]` 的值表示骑士从起始位置到达位置 `(i,j)` 所经过的步数。初始时,我们将起始位置的步数设为1,未访问过的位置的步数设为0。在广度优先搜索算法中,我们使用一个队列 `queue` 来存储待访问的位置。每次从队列中取出一个位置,然后依次将其可达的位置加入队列中,并标记已经访问过的位置的步数,直到找到终点位置或者队列为空为止。最后返回终点位置的步数即可。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值