Floyd 算法 — 卡码网:97. 小明逛公园
题目链接:https://kamacoder.com/problempage.php?pid=1155
文档讲解:https://programmercarl.com/kamacoder/0097.%E5%B0%8F%E6%98%8E%E9%80%9B%E5%85%AC%E5%9B%AD.html
思路
在这之前学习的dijkstra朴素版、dijkstra堆优化、Bellman算法、Bellman队列优化(SPFA) 都是单源最短路,即只能有一个起点。而本题是多源最短路,即 求多个起点到多个终点的多条最短路径,需要使用Floyd算法。
Floyd 算法对边的权值正负没有要求,都可以处理。Floyd算法核心思想是动态规划。
- 确定dp数组(dp table)以及下标的含义
这里用 grid数组来存图,那就把dp数组命名为 grid。·grid[i][j][k] = m·,表示 节点i 到 节点j 以[1…k] 集合为中间节点的最短距离为m。 - 确定递推公式
分两种情况:
2.1. 节点i 到 节点j 的最短路径经过节点k
2.2. 节点i 到 节点j 的最短路径不经过节点k
对于第一种情况,grid[i][j][k] = grid[i][k][k - 1] + grid[k][j][k - 1]
节点i 到 节点k 的最短距离 是不经过节点k,中间节点集合为[1…k-1],所以 表示为grid[i][k][k - 1];节点k 到 节点j 的最短距离 也是不经过节点k,中间节点集合为[1…k-1],所以表示为grid[k][j][k - 1]。
第二种情况,grid[i][j][k] = grid[i][j][k - 1]
如果节点i 到 节点j的最短距离 不经过节点k,那么 中间节点集合[1…k-1],表示为grid[i][j][k - 1];因为我们是求最短路,对于这两种情况自然是取最小值。即:grid[i][j][k] = min(grid[i][k][k - 1] + grid[k][j][k - 1], grid[i][j][k - 1])。 - dp数组如何初始化
grid[i][j][k] = m,表示 节点i 到 节点j 以[1…k] 集合为中间节点的最短距离为m。刚开始初始化k 是不确定的。只能把k赋值为 0,本题节点0是无意义的,节点是从1 到 n。 - 确定遍历顺序
从初始化来看,我们是把 k =0 的 i 和j 对应的数值都初始化了,这样才能去计算 k = 1 的时候 i 和 j 对应的数值。这就好比是一个三维坐标,i 和j 是平层,而k是垂直向上的。遍历的顺序是从底向上 一层一层去遍历。所以遍历k的for循环一定是在最外面,这样才能一层一层去遍历。至于遍历 i 和 j 的话,for 循环的先后顺序无所谓。
空间优化
从滚动数组的角度来看,我们定义一个grid[n + 1][ n + 1][2]这么大的数组就可以,因为k 只是依赖于 k-1的状态,并不需要记录k-2,k-3,k-4 等等这些状态。所以递归公式可以为:grid[i][j] = min(grid[i][j], grid[i][k] + grid[k][j]);。
代码
import java.util.*;
class Main{
public static void main (String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt(), m = in.nextInt();
int[][] grid = new int[n + 1][n + 1];
for (int[] g : grid) Arrays.fill(g, 10005);
for (int i = 0; i < m; i++) {
int u = in.nextInt(), v = in.nextInt(), w = in.nextInt();
grid[u][v] = w;
grid[v][u] = w;
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
grid[i][j] = Math.min(grid[i][j], grid[i][k] + grid[k][j]);
}
}
}
int start, end, val;
int num = in.nextInt();
while (num-- > 0) {
start = in.nextInt();
end = in.nextInt();
System.out.println(grid[start][end] == 10005 ? -1 : grid[start][end]);
}
}
}
A * 算法 — 卡码网:126. 骑士的攻击
题目链接:https://kamacoder.com/problempage.php?pid=1203
文档讲解:https://programmercarl.com/kamacoder/0126.%E9%AA%91%E5%A3%AB%E7%9A%84%E6%94%BB%E5%87%BBastar.html
思路
Astar 是一种广搜的改良版。 有的说Astar是 dijkstra 的改良版。其实只是场景不同而已 我们在搜索最短路的时候, 如果是无权图(边的权值都是1) 那就用广搜,代码简洁,时间效率和 dijkstra 差不多 (具体要取决于图的稠密)。如果是有权图(边有不同的权值),优先考虑 dijkstra。而 Astar 关键在于 启发式函数, 也就是 影响 广搜或者 dijkstra 从 容器(队列)里取元素的优先顺序。以下用BFS版本的A * 来进行讲解。
BFS 是没有目的性的 一圈一圈去搜索, 而 A * 是有方向性的去搜索。A * 为什么可以有方向性的去搜索,它的如何知道方向呢?其关键在于启发式函数。指引搜索的方向的关键代码是:
int m=q.pop();
int n=q.pop();
从队列里取出什么元素,接下来就是从哪里开始搜索。所以启发式函数要影响的就是队列里元素的排序。
每个节点的权值为F,给出公式为:F = G + H
- G:起点达到目前遍历节点的距离
- F:目前遍历的节点到达终点的距离
起点达到目前遍历节点的距离 + 目前遍历的节点到达终点的距离 就是起点到达终点的距离。本题的图是无权网格状,在计算两点距离通常有如下三种计算方式:
- 曼哈顿距离,计算方式:
d = abs(x1-x2)+abs(y1-y2) - 欧氏距离(欧拉距离) ,计算方式:
d = sqrt( (x1-x2)^2 + (y1-y2)^2) - 切比雪夫距离,计算方式:
d = max(abs(x1 - x2), abs(y1 - y2))
本题,采用欧拉距离才能最大程度体现 点与点之间的距离。所以使用欧拉距离计算和广搜搜出来的最短路的节点数是一样的。 计算出来F之后,按照F的大小,来选去出队列的节点。可以使用优先级队列帮我们排好序,每次出队列,就是F最小的节点。
代码
import java.util.*;
class Knight{
public int x, y, g, h, f;
}
class Main {
static int[][] moves = new int[1001][1001];
static int[][] dir = { {-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {2, -1}, {2, 1} };
static int b1, b2;
static PriorityQueue<Knight> queue = new PriorityQueue<>((k1, k2) -> k1.f - k2.f); // 小顶堆
public static void main (String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
while (n-- > 0) {
for (int[] move : moves) Arrays.fill(move, 0);
Knight start = new Knight();
start.x = in.nextInt();
start.y = in.nextInt();
b1 = in.nextInt();
b2 = in.nextInt();
start.g = 0;
start.h = heuristic(start);
start.f = start.g + start.h;
astart(start);
queue.clear();
System.out.println(moves[b1][b2]);
}
}
private static int heuristic(Knight k) {
return (k.x - b1) * (k.x - b1) + (k.y - b2) * (k.y - b2);
}
private static void astart(Knight k) {
queue.offer(k);
Knight cur = new Knight();
while (!queue.isEmpty()) {
cur = queue.poll();
if (cur.x == b1 && cur.y == b2) break;
for (int i = 0; i < 8; i++) {
Knight next = new Knight();
next.x = cur.x + dir[i][0];
next.y = cur.y + dir[i][1];
if (next.x < 1 || next.x > 1000 || next.y < 1 || next.y > 1000) continue;
if (moves[next.x][next.y] == 0) {
moves[next.x][next.y] = moves[cur.x][cur.y] + 1;
next.g = cur.g + 5;
next.h = heuristic(next);
next.f = next.g + next.h;
queue.offer(next);
}
}
}
}
}
最短路算法总结
文档讲解:https://programmercarl.com/kamacoder/%E6%9C%80%E7%9F%AD%E8%B7%AF%E9%97%AE%E9%A2%98%E6%80…
图论总结
文档讲解:https://programmercarl.com/kamacoder/%E5%9B%BE%E8%AE%BA%E6%80%BB%E7%BB%93%E7%AF%87.html

被折叠的 条评论
为什么被折叠?



