Day62-图论,卡码网97,126
- 小明逛公园
-
题目描述:小明喜欢去公园散步,公园内布置了许多的景点,相互之间通过小路连接,小明希望在观看景点的同时,能够节省体力,走最短的路径。 给定一个公园景点图,图中有 N 个景点(编号为 1 到 N),以及 M 条双向道路连接着这些景点。每条道路上行走的距离都是已知的。
-
小明有 Q 个观景计划,每个计划都有一个起点 start 和一个终点 end,表示他想从景点 start 前往景点 end。由于小明希望节省体力,他想知道每个观景计划中从起点到终点的最短路径长度。 请你帮助小明计算出每个观景计划的最短路径长度。
-
输入描述:第一行包含两个整数 N, M, 分别表示景点的数量和道路的数量。 接下来的 M 行,每行包含三个整数 u, v, w,表示景点 u 和景点 v 之间有一条长度为 w 的双向道路。 接下里的一行包含一个整数 Q,表示观景计划的数量。 接下来的 Q 行,每行包含两个整数 start, end,表示一个观景计划的起点和终点。
-
输出描述:对于每个观景计划,输出一行表示从起点到终点的最短路径长度。如果两个景点之间不存在路径,则输出 -1。
-
输入示例
-
7 3
-
2 3 4
-
3 6 6
-
4 7 8
-
2
-
2 3
-
3 4
-
输出示例
-
4
-
-1
- 思路
/**
* 1. 输入处理
使用Node.js的readline模块读取输入数据
第一行读取节点数n和边数m
接下来m行读取边的信息(起点、终点、权重)
最后读取查询数量z和z个查询(起点和终点)
* 2. 数据结构初始化
创建三维数组grid,其中:
grid[i][j][k]表示从节点i到节点j,允许经过前k个节点的最短路径
初始值为10005(比题目中边的最大权重10^4大,相当于无穷大)
* 3. Floyd-Warshall算法核心
外层循环(k):逐步考虑将每个节点作为中间节点
中间循环(i)和内层循环(j):更新所有节点对(i,j)的最短路径
状态转移方程:
不经过节点k的最短路径:grid[i][j][k-1]
经过节点k的最短路径:grid[i][k][k-1] + grid[k][j][k-1]
取两者较小值作为新的最短路径
* 4. 查询处理
读取每个查询的起点和终点
检查grid[start][end][n]的值:
如果仍是10005,说明不可达,输出-1
否则输出最短路径值
* 示例分析
输入
4 5
1 2 2
1 3 4
2 3 1
2 4 5
3 4 1
2
1 4
3 2
执行过程:
1.初始化直接相连的边
2.逐步考虑每个节点作为中间节点:
k=1: 考虑经过节点1的路径
k=2: 考虑经过节点1和2的路径
k=3: 考虑经过节点1、2、3的路径
k=4: 考虑所有节点的路径
3.查询结果:
1→4的最短路径:1→2→3→4,总权重2+1+1=4
3→2的最短路径:3→2,权重1
4.输出
4
1
*/
async function main() {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const lines = [];
for await (const line of rl) {
lines.push(line);
}
let index = 0;
// 读取节点数n和边数m
const [n, m] = lines[index++].split(' ').map(Number);
// 初始化三维数组,grid[i][j][k]表示从i到j经过前k个节点的最短路径
const grid = Array.from({ length: n + 1 }, () =>
Array.from({ length: n + 1 }, () =>
Array(n + 1).fill(10005) // 初始值为10005(比最大边权10^4大)
)
);
// 读取边信息并初始化直接相连的边
for (let i = 0; i < m; i++) {
const [p1, p2, val] = lines[index++].split(' ').map(Number);
grid[p1][p2][0] = val;
grid[p2][p1][0] = val; // 无向图,双向设置
}
// Floyd-Warshall算法核心
for (let k = 1; k <= n; k++) {
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= n; j++) {
// 状态转移方程
grid[i][j][k] = Math.min(
grid[i][j][k - 1], // 不经过k节点
grid[i][k][k - 1] + grid[k][j][k - 1] // 经过k节点
);
}
}
}
// 处理查询
const z = parseInt(lines[index++]);
const results = [];
for (let i = 0; i < z; i++) {
const [start, end] = lines[index++].split(' ').map(Number);
results.push(grid[start][end][n] === 10005 ? -1 : grid[start][end][n]);
}
// 输出所有查询结果
console.log(results.join('\n'));
}
main();
- 骑士的攻击
-
题目描述:在象棋中,马和象的移动规则分别是“马走日”和“象走田”。现给定骑士的起始坐标和目标坐标,要求根据骑士的移动规则,计算从起点到达目标点所需的最短步数。棋盘大小 1000 x 1000(棋盘的 x 和 y 坐标均在 [1, 1000] 区间内,包含边界)
-
输入描述:第一行包含一个整数 n,表示测试用例的数量,1 <= n <= 100。接下来的 n 行,每行包含四个整数 a1, a2, b1, b2,分别表示骑士的起- 始位置 (a1, a2) 和目标位置 (b1, b2)。
-
输出描述:输出共 n 行,每行输出一个整数,表示骑士从起点到目标点的最短路径长度。
-
输入示例
-
6
-
5 2 5 4
-
1 1 2 2
-
1 1 8 8
-
1 1 8 7
-
2 1 3 3
-
4 6 4 6
-
输出示例
-
2
-
4
-
6
-
5
-
1
-
0
- 思路
// 1. MinHeap 最小堆实现
class MinHeap {
constructor() {
this.val = [] // 存储堆元素的数组
}
// 添加元素到堆中
push(val) {
this.val.push(val)
if (this.val.length > 1) {
this.bubbleUp() // 添加后调整堆结构
}
}
// 上浮操作,保持堆性质
bubbleUp() {
let pi = this.val.length - 1 // 当前节点索引
let pp = Math.floor((pi - 1) / 2) // 父节点索引
// 如果当前节点值小于父节点值,交换它们
while (pi > 0 && this.val[pp][0] > this.val[pi][0]) {
;[this.val[pi], this.val[pp]] = [this.val[pp], this.val[pi]]
pi = pp
pp = Math.floor((pi - 1) / 2)
}
}
// 弹出堆顶最小元素
pop() {
if (this.val.length > 1) {
let pp = 0
let pi = this.val.length - 1
;[this.val[pi], this.val[pp]] = [this.val[pp], this.val[pi]] // 交换首尾元素
const min = this.val.pop() // 移除并返回原堆顶元素
if (this.val.length > 1) {
this.sinkDown(0) // 下沉新堆顶元素
}
return min
} else if (this.val.length == 1) {
return this.val.pop()
}
}
// 下沉操作,保持堆性质
sinkDown(parentIdx) {
let pp = parentIdx
let plc = pp * 2 + 1 // 左子节点索引
let prc = pp * 2 + 2 // 右子节点索引
let pt = pp // 临时指针
// 找出父节点和两个子节点中的最小值
if (plc < this.val.length && this.val[pp][0] > this.val[plc][0]) {
pt = plc
}
if (prc < this.val.length && this.val[pt][0] > this.val[prc][0]) {
pt = prc
}
// 如果最小值不是父节点,交换并继续下沉
if (pt != pp) {
;[this.val[pp], this.val[pt]] = [this.val[pt], this.val[pp]]
this.sinkDown(pt)
}
}
}
// 2. 辅助函数和常量
// 骑士的8种可能移动方式
const moves = [
[1, 2], // 右1上2
[2, 1], // 右2上1
[-1, -2], // 左1下2
[-2, -1], // 左2下1
[-1, 2], // 左1上2
[-2, 1], // 左2上1
[1, -2], // 右1下2
[2, -1] // 右2下1
]
// 计算两点之间的欧几里得距离
function dist(a, b) {
return ((a[0] - b[0])**2 + (a[1] - b[1])**2)**0.5
}
// 检查坐标是否在有效范围内(1-1000)
function isValid(x, y) {
return x >= 1 && y >= 1 && x < 1001 && y < 1001
}
// 3. A*搜索算法实现
function bfs(start, end) {
const step = new Map() // 记录到达每个位置的最小步数
step.set(start.join(" "), 0) // 起点步数为0
const q = new MinHeap() // 优先队列(最小堆)
// 初始状态: [启发式估计值, x坐标, y坐标]
q.push([dist(start, end), start[0], start[1]])
while(q.val.length) {
const [d, x, y] = q.pop() // 取出当前最优候选位置
// 如果到达终点,输出结果并结束
if (x == end[0] && y == end[1]) {
console.log(step.get(end.join(" ")))
break;
}
// 尝试所有可能的移动方式
for (const [dx, dy] of moves) {
const nx = dx + x // 新x坐标
const ny = dy + y // 新y坐标
if (isValid(nx, ny)) { // 检查新位置是否有效
const newStep = step.get([x, y].join(" ")) + 1 // 新步数
const newDist = dist([nx, ny], [...end]) // 新位置到终点的距离
// 获取该位置已知的最小步数(如果没有则设为无穷大)
const s = step.get([nx, ny].join(" ")) ?
step.get([nx, ny].join(" ")) :
Number.MAX_VALUE
// 如果找到更短的路径
if (newStep < s) {
// 将新位置加入优先队列,优先级为(步数+启发式估计值)
q.push([newStep + newDist, nx, ny])
// 更新该位置的最小步数
step.set([nx, ny].join(" "), newStep)
}
}
}
}
}
// 4. 主函数
async function main() {
const rl = require('readline').createInterface({ input: process.stdin })
const iter = rl[Symbol.asyncIterator]()
const readline = async () => (await iter.next()).value
const n = Number((await readline())) // 读取测试用例数量
// 处理每个测试用例
for (let i = 0 ; i < n ; i++) {
const [s1, s2, t1, t2] = (await readline()).split(" ").map(Number)
bfs([s1, s2], [t1, t2]) // 调用A*搜索算法
}
}
main()

浙公网安备 33010602011771号