刷题学习心得:图的最短路径问题解析

86 阅读6分钟

刷题学习心得:图的最短路径问题解析

在编程学习的旅程中,利用豆包和 MarsCode AI 刷题平台,我接触到了图相关的算法问题,其中图的最短路径问题尤为引人入胜。在攻克这些问题的过程中,我积累了丰富的知识和宝贵的经验。

题目解析:Dijkstra 算法求解单源最短路径问题

思路

Dijkstra 算法用于解决带非负权值的有向图或无向图的单源最短路径问题。其核心思路是维护一个顶点集合,将顶点分为已确定最短路径的顶点和未确定最短路径的顶点。

算法开始时,源点到自身的距离为 0,到其他顶点的距离设为无穷大。然后,每次从尚未确定最短路径的顶点中选择距离源点最短的顶点,将其加入已确定最短路径的顶点集合,并对与该顶点相邻的未确定顶点的距离进行更新。重复这个过程,直到所有顶点都被确定最短路径。

图解

假设有一个简单的带权无向图,顶点有 A、B、C、D、E,边的权重如下:A - B(3),A - C(5),B - C(1),B - D(6),C - D(2),C - E(4),D - E(2)。

  • 以 A 为源点,初始化距离数组,dist [A]=0,dist [B]=∞,dist [C]=∞,dist [D]=∞,dist [E]=∞。
  • 首先选择 A,更新其相邻顶点 B 和 C 的距离,dist [B]=3,dist [C]=5。此时距离源点 A 最近的未确定顶点是 B。
  • 选择 B,更新其相邻顶点 C 和 D 的距离,因为通过 B 到 C 的距离 3 + 1 = 4 小于之前的 5,所以更新 dist [C]=4,dist [D]=9。此时距离源点 A 最近的未确定顶点是 C。
  • 选择 C,更新其相邻顶点 D 和 E 的距离,dist [D]=6(4 + 2),dist [E]=8(4 + 4)。此时距离源点 A 最近的未确定顶点是 D。
  • 选择 D,更新 E 的距离,因为通过 D 到 E 的距离 6 + 2 = 8,与之前相同,无需更新。最后所有顶点都确定了最短路径。

代码详解(Java)

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;

class Vertex {
    int id;
    List<Edge> neighbors;

    Vertex(int id) {
        this.id = id;
        this.neighbors = new ArrayList<>();
    }
}

class Edge {
    Vertex target;
    int weight;

    Edge(Vertex target, int weight) {
        this.target = target;
        this.weight = weight;
    }
}

public class Dijkstra {
    public static Map<Integer, Integer> dijkstra(Vertex source) {
        Map<Integer, Integer> distance = new HashMap<>();
        PriorityQueue<Vertex> pq = new PriorityQueue<>((v1, v2) -> distance.getOrDefault(v1.id, Integer.MAX_VALUE) - distance.getOrDefault(v2.id, Integer.MAX_VALUE));

        for (Vertex vertex : allVertices) {
            if (vertex == source) {
                distance.put(vertex.id, 0);
            } else {
                distance.put(vertex.id, Integer.MAX_VALUE);
            }
            pq.add(vertex);
        }

        while (!pq.isEmpty()) {
            Vertex current = pq.poll();
            for (Edge edge : current.neighbors) {
                int newDistance = distance.get(current.id) + edge.weight;
                if (newDistance < distance.get(edge.target.id)) {
                    distance.put(edge.target.id, newDistance);
                    pq.remove(edge.target);
                    pq.add(edge.target);
                }
            }
        }

        return distance;
    }

    public static void main(String[] args) {
        // 构建图
        Vertex A = new Vertex(0);
        Vertex B = new Vertex(1);
        Vertex C = new Vertex(2);
        Vertex D = new Vertex(3);
        Vertex E = new Vertex(4);

        A.neighbors.add(new Edge(B, 3));
        A.neighbors.add(new Edge(C, 5));
        B.neighbors.add(new Edge(C, 1));
        B.neighbors.add(new Edge(D, 6));
        C.neighbors.add(new Edge(D, 2));
        C.neighbors.add(new Edge(E, 4));
        D.neighbors.add(new Edge(E, 2));

        List<Vertex> allVertices = new ArrayList<>();
        allVertices.add(A);
        allVertices.add(B);
        allVertices.add(C);
        allVertices.add(D);
        allVertices.add(E);

        Map<Integer, Integer> result = dijkstra(A);
        for (Map.Entry<Integer, Integer> entry : result.entrySet()) {
            System.out.println("顶点 " + entry.getKey() + " 到源点的最短距离: " + entry.getValue());
        }
    }
}

在上述代码中:

  • 首先定义了Vertex类表示图的顶点,包含顶点的id和邻居列表。Edge类表示边,包含目标顶点和边的权重。
  • dijkstra方法中,使用distance映射存储每个顶点到源点的距离,使用优先队列pq来选择距离源点最近的未确定顶点。
  • 初始化阶段,设置源点距离为 0,其他顶点距离为无穷大,并将所有顶点加入优先队列。
  • 在主循环中,取出距离源点最近的顶点,更新其相邻顶点的距离。如果通过当前顶点到相邻顶点的距离更短,则更新距离并重新调整优先队列。
  • main方法中构建了示例图,并调用dijkstra方法计算从顶点 A 出发的最短路径,然后输出结果。

知识总结

贪心策略在 Dijkstra 算法中的应用

Dijkstra 算法本质上是一种贪心算法。它每次选择当前距离源点最短的未确定顶点,这种贪心选择保证了最终能得到全局最优解。其正确性基于边的权值非负这一前提,因为在非负权值情况下,先选择的短路径不会因为后续的选择而变长。理解贪心策略在其中的应用,有助于我们在其他类似问题中思考是否可以采用贪心思想来求解。

数据结构的选择与优化

在实现 Dijkstra 算法时,选择合适的数据结构至关重要。这里使用了优先队列来高效地获取距离源点最短的顶点,其时间复杂度为 (V 是顶点数)。同时,使用哈希表(这里是Map)来存储顶点到源点的距离,方便快速查找和更新。这种数据结构的组合优化了算法的效率。此外,还可以进一步考虑对优先队列进行优化,比如使用索引堆等更高级的数据结构来提高性能。

图的表示与遍历

通过这个问题,我对图的表示和遍历有了更深入的理解。图可以用邻接表或邻接矩阵表示,这里采用邻接表的方式,方便存储和遍历顶点的邻居。在遍历过程中,从源点开始逐步扩展到其他顶点,这种以顶点为中心的遍历方式与树的遍历有相似之处,但由于图可能存在环,需要更复杂的逻辑来处理。

学习建议

理解算法原理,多手动模拟

对于 Dijkstra 算法这类复杂的图算法,要深入理解其原理。可以通过手动在纸上模拟算法在简单图上的执行过程,就像在图解部分所做的那样。这样能直观地看到顶点距离的更新和最短路径的确定过程,有助于理解贪心策略和算法的整体流程。

实践不同类型的图问题

除了最短路径问题,图还有许多其他类型的问题,如最小生成树问题、拓扑排序问题等。多实践不同类型的问题可以加深对图这种数据结构的理解,掌握不同图算法的应用场景和特点。同时,在实践过程中可以对比不同算法,理解它们在时间复杂度、空间复杂度和适用场景上的差异。

关注算法的优化和扩展

在掌握基本的图算法后,要关注算法的优化和扩展。例如,对于 Dijkstra 算法,可以研究如何处理负权边(虽然 Dijkstra 算法本身不能直接处理,但可以了解其他相关算法),以及在分布式环境下如何实现最短路径计算等。这种对算法优化和扩展的研究可以拓宽知识面,提高解决复杂问题的能力。

总之,图的最短路径问题是编程学习中的一个重要挑战,通过深入学习 Dijkstra 算法等相关知识,我们不仅能解决这类具体问题,还能提升对数据结构和算法的综合理解能力,为进一步的编程学习打下坚实的基础。希望这些经验对其他同学在学习图算法时有所帮助。