Day62-图论,卡码网97,126

  1. 小明逛公园
  • 题目描述:小明喜欢去公园散步,公园内布置了许多的景点,相互之间通过小路连接,小明希望在观看景点的同时,能够节省体力,走最短的路径。 给定一个公园景点图,图中有 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();


  1. 骑士的攻击
  • 题目描述:在象棋中,马和象的移动规则分别是“马走日”和“象走田”。现给定骑士的起始坐标和目标坐标,要求根据骑士的移动规则,计算从起点到达目标点所需的最短步数。棋盘大小 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()




参考&感谢各路大神

posted @ 2025-07-28 10:12  安静的嘶吼  阅读(7)  评论(0)    收藏  举报