罗密欧与朱丽叶的迷宫问题
问题描述
罗密欧与朱丽叶的迷宫问题
罗密欧与朱丽叶身处一个 $m×n $的迷宫中,如图所示。每一个方格表示迷宫中的一个房间。这 m × n m×n m×n 个房间中有一些房间是封闭的,不允许任何人进入。在迷宫中任何位置均可沿 8 个方向进入未封闭的房间。罗密欧位于迷宫的 ( p , q ) (p,q) (p,q)方格中,他必须找出一条通向朱丽叶所在的 ( r , s ) (r,s) (r,s)方格的路。在抵达朱丽叶之前,他必须走遍所有未封闭的房间各一次,而且要使到达朱丽叶的转弯次数为最少。每改变一次前进方向算作转弯一次。请设计一个算法帮助罗密欧找出这样一条道路。
算法分析与设计
分析
对于给定的罗密欧与朱丽叶的迷宫,编程计算罗密欧通向朱丽叶的所有最少转弯道路。
整体思路:
- 首先需要先处理输入,记录封闭房间,两人的位置
- 回溯法查找最优路线
- 输出结果
回溯法细节
框架可以直接套用课上讲的回溯法的基本框架
- 判断结束的条件为:如果所有方格走遍,且到达朱丽叶位置,且转弯次数<=最优转弯次数
- 当前路线的转弯次数小于最优的转弯次数的话需要更新记录值
- 否则只需要将路线数+1即可
- 然后需要遍历八个可走的方向进行回溯,在每个方向上均需要计算出下一步的坐标,然后判断下一步是否满足约束函数
- 如果满足就进入下一步继续回溯
- 如果不满足则跳过判断下一个方向是否可走
函数解释
- 函数
void save()
保存找到的最优路线; - 函数
boolean ok(int x, int y)
用于判断是否越界和可通过; - 函数
void output()
用于输出结果; - 函数
void backtrack(int x, int y, int dep, int di)
用于回溯求解
代码说明
Solution.java
package edu.xjtu.work5_12;
import java.util.Scanner;
public class Solution {
static final int[] dx = {1, 0, -1, 0, 1, 1, -1, -1}; //八个方向
static final int[] dy = {0, 1, 0, -1, 1, -1, 1, -1};
static Point luo = new Point();// 罗密欧位置
static Point ye = new Point();// 朱丽叶位置
static int MAX = 10;
static final int[][] board = new int[MAX][MAX];
static final int[][] best = new int[MAX][MAX];
static int n, m, k;
static int dirs = 0; //转弯次数
static int min = 100000; //最少转弯次数
static int count = 0; //不同的最少转弯道路数
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
n = input.nextInt();// 读入行数
m = input.nextInt();// 读入列数
k = input.nextInt();// 封闭房间数
int x, y;
// 封闭房间所在的行号和列号
for (int i = 1; i <= k; i++) {
x = input.nextInt();
y = input.nextInt();
board[x][y] = -1; //标记封闭房间
}
// 罗密欧的位置
luo.x = input.nextInt();
luo.y = input.nextInt();
// 朱丽叶的位置
ye.x = input.nextInt();
ye.y = input.nextInt();
input.close();
board[luo.x][luo.y] = 1;
backtrack(luo.x, luo.y, 1, 0);// 回溯法求解
output();// 输出结果
}
/**
* 回溯查找结果
*
* @param x 坐标x
* @param y 坐标y
* @param dep 总深度
* @param di 当前深度
*/
static void backtrack(int x, int y, int dep, int di) {
//如果所有方格走遍,且到达朱丽叶位置,且转弯次数<=最优转弯次数
if (dep == n * m - k && x == ye.x && y == ye.y && dirs <= min) {
if (dirs < min) //如果<最优转弯次数
{
min = dirs;
count = 1; //重新记录道路数
save();
} else
count++; //否则道路数增加
return;
}
if (dep == n * m - k && x == ye.x && y == ye.y && dirs > min)
return;
for (int i = 0; i < 8; i++) //可走的八个方向
{
int nextx = x + dx[i]; //计算下一步坐标
int nexty = y + dy[i];
if (ok(nextx, nexty)) //如果下一步可行
{
board[nextx][nexty] = dep + 1;
if (dep > 1 && di != i) //第一步不算转弯,如果方向不同,转弯次数增加
dirs++;
backtrack(nextx, nexty, dep + 1, i);
board[nextx][nexty] = 0; //回溯
if (dep > 1 && di != i)
dirs--;
}
}
}
/**
* 输出结果
*/
static void output() {
System.out.println(min);
System.out.println(count);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
System.out.print(best[i][j] + "\t");
System.out.println();
}
}
/**
* 判断是否满足约束函数
*
* @param x 坐标x
* @param y 坐标y
* @return 是否满足
*/
static boolean ok(int x, int y) {
//在迷宫内且未走到封闭房间
return x > 0 && y > 0 && x <= n && y <= m && board[x][y] == 0;
}
/**
* 保存最优解
*/
static void save() {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
best[i][j] = board[i][j];
}
}
Point.java
package edu.xjtu.work5_12;
public class Point {
int x;
int y;
}