The Shortest Path of Graphs | 图的最短路问题3

Albert Wang / 2023-05-02 / 500 Words/has been Read   Times


对于下面这个 3 * 3 的矩阵来说,如果我们想要从从左上角走到右下角,每次只能走一步,问怎么走距离最短。

假如现在有一个问题,对于下面这个 3 * 3 的矩阵来说,如果我们想要从从左上角走到右下角,每次只能走一步,问怎么走距离最短?

img

这个题目我们一般就直接采用 BFS 计算出结果,这样做无疑是最方便的。但是我们同样可以先构建图,然后再使用 dijkstra 算法来计算最小路径。上面的矩阵可以被表示为下面这样的一张图,

image-20230502210303117

但是我们用邻接表表示这张图就稍微有点困难了,因为一般来说我们表示图的某一个节点一般是用一个数字 i ( 0 <= i < n) 来表示的,但是这回的顶点确是用(i, j)这一个点对来表示的。所以我们邻接表的数据结构用数组 + 链表就不太方便了,这时可以用 Map 来表示。

在 Java 语言的实现中一般是要用 HashMap,这时就要考虑可能出现哈希碰撞的情况,比较有效的一个办法是重写 hashCode 函数,如下图中的 Pair 对象。关于为什么要重写 hashCode 函数可以参考我这篇博客

class Pair {
    int x;
    int y;
    int distance;

    public Pair(int x, int y, int distance) {
        this.x = x;
        this.y = y;
        this.distance = distance;
    }

    public int hashCode() {
        return (x << 16) | y;
    }  
    public boolean equals(Object a) {
        Pair obj = (Pair)a;
        if (obj == null) {
            return false;
        }

        return this.x == obj.x && this.y == obj.y;
    }
}

然后就是建图的过程了,这个和一维的比较类似,这里不多阐述。

    public void addEdge(HashMap<Pair, List<Pair>> graph, Pair a, Pair b, int m, int n) {
        if (a.x < 0 || a.x >= m || a.y < 0 || a.y >= n) {
            return;
        }
        List<Pair> list = graph.getOrDefault(a, null);
        if (list == null) {
            list = new ArrayList<>();
        }

        list.add(b);
        graph.put(a, list);
    }

    public int init(int[] start, int[] target) {
        int m = target[0] + 1;
        int n = target[1] + 1;

        HashMap<Pair, List<Pair>> graph = new HashMap<>();

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                Pair b = new Pair(i, j, 1);
                addEdge(graph, new Pair(i - 1, j, 1), b, m, n);
                addEdge(graph, new Pair(i + 1, j, 1), b, m, n);
                addEdge(graph, new Pair(i, j + 1, 1), b, m, n);
                addEdge(graph, new Pair(i, j - 1, 1), b, m, n);
            }
        }
        return dijkstra(graph, new Pair(start[0], start[1], 1), new Pair(target[0], target[1], 1), m, n);
    }

需要注意的是在 Pair 对象的创建中我们都要初始化一个从 a 点到 b 点的距离。比如下面的代码表示添加一条从 a 到 b 的边,这条边的权重是 1,由 b 这个对象的 distance 字段来表示。这里 a 的 distance 字段可以填任意值,因为我们重写了 Pair 的 equals 函数,比较两个对象是否相同的时候只会比较它们是否表示了同一个点。

Pair a = new Pair(i - 1, j, 1);
Pair b = new Pair(i, j, 1);
addEdge(graph, a, b, m, n);

下面是 dijkstra 的实现,仅供参考。

    public int dijkstra(HashMap<Pair, List<Pair>> graph, Pair start, Pair end, int m, int n) {
        int[][] distance = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                distance[i][j] = Integer.MAX_VALUE;
            }
        }

        distance[start.x][start.y] = 0;
        PriorityQueue<Pair> pq = new PriorityQueue<>((a, b) -> (a.distance - b.distance));
        pq.add(new Pair(start.x, start.y, 0));
        while (!pq.isEmpty()) {
            Pair cur = pq.poll();
            if (distance[cur.x][cur.y] != cur.distance) { // 已经是固定标号点要退出
                continue;
            }
            List<Pair> list = graph.get(cur);
            for (int i = 0; i < list.size(); i++) {
                Pair pair = list.get(i);

                if (distance[cur.x][cur.y] + pair.distance < distance[pair.x][pair.y]) {
                    distance[pair.x][pair.y] = distance[cur.x][cur.y] + pair.distance;
                    pq.add(new Pair(pair.x, pair.y, distance[cur.x][cur.y] + pair.distance));
                }
            }
        }
        return distance[end.x][end.y] == Integer.MAX_VALUE ? -1 : distance[end.x][end.y];
    }

Last modified on 2023-05-02