假定街道是棋盘型的,每格距离相等,车辆通过每格街道需要时间均为 timePerRoad;
街道的街口(交叉点)有交通灯,灯的周期 T(=lights[row][col])各不相同;
车辆可直行、左转和右转,其中直行和左转需要等相应 T 时间的交通灯才可通行,右转无需等待。
现给出 n * m 个街口的交通灯周期,以及起止街口的坐标,计算车辆经过两个街口的最短时间。
其中:
入口函数定义:
/**
* @param lights:n*m个街口每个交通灯的周期,值范围[0, 120],n和m的范围为[1,9]
* @param timePreRoad:相邻两个街口之间街道的通行时间,范围为[0,600]
* @param rowStart:起点的行号
* @param colStart:起点的列号
* @param rowEnd:终点的行号
* @param colEnd:终点的列号
* @return lights[rowStart][colStart] 与 lights[rowEnd][colEnd] 两个街口之间的最短通行时间
*/
int calcTime(int[][] lights, int timePreRoad, int rowStart, int colStart, int rowEnd, int colEnd)
第一行输入 n 和 m,以空格分隔
之后 n 行输入 lights矩阵,矩阵每行m个整数,以空格分隔
之后一行输入 timePerRoad
之后一行输入 rowStart colStart,以空格分隔
最后一行输入 rowEnd colEnd,以空格分隔
lights[rowStart][colStart] 与 lights[rowEnd][colEnd] 两个街口之间的最短通行时间
输入
3 3
1 2 3
4 5 6
7 8 9
60
0 0
2 2
输出
245
说明
行走路线为 (0,0) -> (0,1) -> (1,1) -> (1,2) -> (2,2) 走了4格路,2个右转,1个左转,共耗时 60+0+60+5+60+0+60=245
暴力解法回溯出所有路线,计算距离
在有权图(权值非负数)中求从起点到其他节点的最短路径算法: dijkstra 算法(dijkstra 算法可以同时求 起点到所有节点的最短路径)
dijkstra:
注意:到达当前节点的最短距离,不一定能推导出下一个节点的最小距离
例如: 到达(1,2)的距离(0,0) -> (0,1) -> (0,2) ->(1,2) : 60*3+2+0 = 182;比(0,0) -> (0,1) -> (1,1) -> (1,2) 60*3+5+0 = 185; 182的线路小,但是这个线路不能推导出(2,2)的最优解, 所以minDist在存储时不能只存储距离,还要存储方向信息。
方向编号处理: 顺时针规定方向编号,从0开始
路口编号处理: 按照先行后列设置编号,从0开始(目的为了降低数组维度,降低for循环嵌套)
public class Demo20 {
// 方向顺序
private static final int[][] dir = new int[][] {
{-1, 0},
{0, 1},
{1, 0},
{0, -1},
};
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNextInt()) {
int rows = in.nextInt();
int cols = in.nextInt();
int[][] lights = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
lights[i][j] = in.nextInt();
}
}
int timePerRoad = in.nextInt();
int rowStart = in.nextInt();
int colStart = in.nextInt();
int rowEnd = in.nextInt();
int colEnd = in.nextInt();
System.out.println(calcTime(lights, timePerRoad, rowStart, colStart, rowEnd, colEnd));
}
in.close();
}
/**
* @param lights:n*m个街口每个交通灯的周期,值范围[0, 120],n和m的范围为[1,9]
* @param timePreRoad:相邻两个街口之间街道的通行时间,范围为[0,600]
* @param rowStart:起点的行号
* @param colStart:起点的列号
* @param rowEnd:终点的行号
* @param colEnd:终点的列号
* @return lights[rowStart][colStart] 与 lights[rowEnd][colEnd] 两个街口之间的最短通行时间
*/
static int calcTime(int[][] lights, int timePreRoad, int rowStart, int colStart, int rowEnd, int colEnd) {
int startNum = toNumber(rowStart, colStart, lights);
int count = lights.length * lights.length;
//非访问节点到源点的时间
int[][] minDist = new int[count + 1][4];
for (int[] ints : minDist) {
Arrays.fill(ints, Integer.MAX_VALUE);
}
minDist[startNum][0] = 0;
minDist[startNum][1] = 0;
minDist[startNum][2] = 0;
minDist[startNum][3] = 0;
// 是否访问
boolean[][] visited = new boolean[count + 1][4];
// [isFirst, number, dir, dist]
PriorityQueue<Integer[]> queue = new PriorityQueue<>(Comparator.comparingInt(t0 -> t0[3]));
queue.offer(new Integer[] {1, startNum, 0, 0});
while (!queue.isEmpty()) {
//- 第一步,选源点到哪个节点近且该节点未被访问过
Integer[] curNode = queue.poll();
boolean isFirst = curNode[0] == 1;
int curNumber = curNode[1];
int curDir = curNode[2];
if (visited[curNumber][curDir]) {
continue;
}
//- 第二步,该最近节点被标记访问过
visited[curNumber][curDir] = true;
int[] ints = toXY(curNumber, lights);
int curRow = ints[0];
int curCol = ints[1];
//- 第三步,更新非访问节点到源点的距离(即更新minDist数组)
for (int j = 0; j < dir.length; j++) {
// 不能去 来的方向
if (!isFirst && oppositeDir(j) == curDir) {
continue;
}
int nextX = curRow + dir[j][0];
int nextY = curCol + dir[j][1];
if (!legalXY(nextX, nextY, lights)) {
continue;
}
int nextNumber = toNumber(nextX, nextY, lights);
if (visited[nextNumber][j]) {
continue;
}
int wait = 0;
if (!isFirst) {
wait = calcWaitTime(lights, curDir, curRow, curCol, j);
}
int needTime = wait + timePreRoad;
if (needTime + minDist[curNumber][curDir] < minDist[nextNumber][j]) {
minDist[nextNumber][j] = needTime + minDist[curNumber][curDir];
// [isFirst, number, dir, dist]
queue.offer(new Integer[] {0, nextNumber, j, minDist[nextNumber][j]});
}
}
}
int endNumber = toNumber(rowEnd, colEnd, lights);
int dist = Integer.MAX_VALUE;
for (int i = 0; i < 4; i++) {
int d = minDist[endNumber][i];
if (d < dist) {
dist = d;
}
}
return dist;
}
// 结算等待时间
public static int calcWaitTime(int[][] lights, int dirFlag, int x, int y, int nextDirFlag) {
// 右转判断
if ((nextDirFlag + 1) % 4 == dirFlag) {
return 0;
} else {
return lights[x][y];
}
}
// 反方向计算
public static int oppositeDir(int dir) {
return (dir + 2) % 4;
}
// 坐标转编号
public static int toNumber(int x, int y, int[][] lights) {
return x * lights.length + y;
}
// 编号转坐标
public static int[] toXY(int number, int[][] lights) {
int curCol = number % lights.length;
int curRow = number / lights.length;
return new int[] {curRow, curCol};
}
// 合法坐标
public static boolean legalXY(int x, int y, int[][] lights) {
return x >= 0 && x < lights.length && y >= 0 && y < lights[0].length;
}
}