0

0

矩阵中四向移动最短路径和的高效 Dijkstra 实现指南

花韻仙語

花韻仙語

发布时间:2026-02-26 13:51:10

|

254人浏览过

|

来源于php中文网

原创

矩阵中四向移动最短路径和的高效 Dijkstra 实现指南

本文详解如何在支持上下左右移动的矩阵中,用正确、高效的 dijkstra 算法求解从左上角到右下角的最小路径和,并指出常见实现错误(如重复入堆、未及时剪枝)及优化要点。

本文详解如何在支持上下左右移动的矩阵中,用正确、高效的 dijkstra 算法求解从左上角到右下角的最小路径和,并指出常见实现错误(如重复入堆、未及时剪枝)及优化要点。

在二维矩阵中求解「允许向四个方向(上、下、左、右)移动」的最短路径和问题(如 Project Euler #83 或 HackerRank 对应题),本质上是带权无向图上的单源最短路径问题。虽然 BFS 适用于边权为 1 的场景,但本题中每个格子的值即为进入该格的代价,边权不等且非负,因此 Dijkstra 算法不仅是合理选择,更是理论最优解法——其时间复杂度为 $O(E \log V)$,对 $N \times N$ 矩阵而言,$V = N^2$,$E \approx 4N^2$,即 $O(N^2 \log N^2) = O(N^2 \log N)$,完全可应对 $700 \times 700$ 规模(约 49 万节点)。

然而,实践中许多实现(尤其是手写优先队列版本)会因逻辑瑕疵导致性能断崖式下降。核心陷阱在于:未在出堆时立即检查并跳过已松弛过的节点,从而造成同一节点被多次入堆、多次处理,将时间复杂度劣化至接近 $O(E \cdot \log E)$,甚至触发大量冗余计算。

❌ 典型错误实现(低效根源)

以下 Go 片段展示了常见误写:

for frontier.Len() > 0 {
    element := heap.Pop(&frontier).(*Item)
    vertex, cost := element.value, element.priority
    visited[vertex] = true // ❌ 错误:标记太晚!此时可能已重复入堆多次
    for vertex_new, cost_new := range graph[vertex] {
        if !visited[vertex_new] {
            if vertex_new == end {
                return cost + cost_new
            }
            heap.Push(&frontier, &Item{
                value:    vertex_new,
                priority: cost + cost_new,
            })
        }
    }
}

问题在于:visited[vertex] = true 在 Pop 后才执行,而此前完全可能因不同路径多次将同一 vertex 推入堆中。例如,节点 (1,1) 可能通过 (0,1)→(1,1) 和 (1,0)→(1,1) 两条路径以不同代价入堆。若不加判断即全部入堆,堆大小会急剧膨胀,heap.Push/Pop 开销剧增,实测 700×700 矩阵下运行时间从

✅ 正确实现:延迟标记 + 出堆校验

正确做法是:每次 Pop 后,先检查该节点是否已被更优路径松弛过;若是,则直接跳过,不作任何处理。这称为 “lazy deletion”(惰性删除),是标准 Dijkstra 的关键实践:

厉害猫AI
厉害猫AI

遥遥领先的AI全职业办公写作平台

下载
for frontier.Len() > 0 {
    element := heap.Pop(&frontier).(*Item)
    vertex, cost := element.value, element.priority

    // ✅ 关键校验:若当前取出的 cost 大于已知最短距离,说明已过时,跳过
    if cost > dist[vertex] {
        continue
    }

    // 若已到达终点,直接返回(注意:此处 dist[vertex] 即为最短距离)
    if vertex == end {
        return cost
    }

    for vertex_new, edge_cost := range graph[vertex] {
        new_cost := cost + edge_cost
        if new_cost < dist[vertex_new] { // ✅ 松弛条件
            dist[vertex_new] = new_cost
            heap.Push(&frontier, &Item{
                value:    vertex_new,
                priority: new_cost,
            })
        }
    }
}

? 提示:使用 dist[] 数组替代 visited[] 布尔数组更鲁棒。初始化 dist[i] = +∞,dist[start] = matrix[0][0]。dist[v] 始终表示当前已知的起点到 v 的最短距离,cost > dist[vertex] 即为过时节点判定依据。

? 进阶优化建议

  1. 避免显式建图:无需预先构建邻接表 map[int]map[int]int。直接在 Dijkstra 主循环中根据坐标 (i,j) 动态计算四个邻居 (i±1,j), (i,j±1),节省内存与预处理时间。

  2. 数据类型安全:矩阵值可达 $10^9$,700×700 路径和可能超 int32(甚至 int64 在极端链式累加下需谨慎)。Java 示例中使用 long,Go 中应统一用 int64 存储距离与堆优先级。

  3. 优先队列实现:确保 Less(i,j) 按 小根堆 定义(即 pq[i].priority

  4. 起点处理:起点 (0,0) 的代价应为 matrix[0][0],而非 0。路径和包含起点值,终点值也必须计入。

✅ 完整 Go 核心逻辑(精简版)

type Item struct {
    row, col int
    dist     int64
}
type PriorityQueue []*Item

func (pq PriorityQueue) Less(i, j int) bool { return pq[i].dist < pq[j].dist }
func (pq PriorityQueue) Swap(i, j int)      { pq[i], pq[j] = pq[j], pq[i]; pq[i].index, pq[j].index = i, j }
func (pq *PriorityQueue) Push(x interface{}) { *pq = append(*pq, x.(*Item)) }
func (pq *PriorityQueue) Pop() interface{} {
    old := *pq
    n := len(old)
    item := old[n-1]
    *pq = old[0 : n-1]
    return item
}

func minPathSum(matrix [][]int64) int64 {
    n := len(matrix)
    dist := make([][]int64, n)
    for i := range dist {
        dist[i] = make([]int64, n)
        for j := range dist[i] {
            dist[i][j] = math.MaxInt64
        }
    }
    dist[0][0] = matrix[0][0]

    pq := &PriorityQueue{}
    heap.Init(pq)
    heap.Push(pq, &Item{0, 0, matrix[0][0]})

    dirs := [4][2]int{{-1, 0}, {0, -1}, {1, 0}, {0, 1}} // 上、左、下、右

    for pq.Len() > 0 {
        cur := heap.Pop(pq).(*Item)
        if cur.dist > dist[cur.row][cur.col] {
            continue
        }
        if cur.row == n-1 && cur.col == n-1 {
            return cur.dist
        }

        for _, d := range dirs {
            r, c := cur.row+d[0], cur.col+d[1]
            if r >= 0 && r < n && c >= 0 && c < n {
                newDist := cur.dist + matrix[r][c]
                if newDist < dist[r][c] {
                    dist[r][c] = newDist
                    heap.Push(pq, &Item{r, c, newDist})
                }
            }
        }
    }
    return dist[n-1][n-1]
}

? 总结

  • Dijkstra 是解决本题的理论最优且实践可行的方法,无需转向 A* 或 DP(后者因四向移动失去无后效性)。
  • 性能瓶颈几乎总是源于未做出堆校验,导致节点重复处理。务必采用 dist[] 数组 + if cost > dist[v] continue 模式。
  • 避免预建图、使用合适整数类型、确保最小堆语义,三者结合可轻松在 4 秒内完成 700×700 矩阵计算。
  • 最终答案即 dist[n-1][n-1],它天然包含起点与终点的权重,符合题目“路径和”定义。

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

207

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

242

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

350

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

213

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

405

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

385

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

200

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

1131

2025.06.17

Golang 实际项目案例:从需求到上线
Golang 实际项目案例:从需求到上线

《Golang 实际项目案例:从需求到上线》以真实业务场景为主线,完整覆盖需求分析、架构设计、模块拆分、编码实现、性能优化与部署上线全过程,强调工程规范与实践决策,帮助开发者打通从技术实现到系统交付的关键路径,提升独立完成 Go 项目的综合能力。

1

2026.02.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号