算法——图的最短距离

引言

本文整理了常见的图的最短距离相关算法,方便以后查阅。更多相关文章和其他文章均收录于贝贝猫的文章目录

BellmanFord

package bbm.graph;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * 解决单源最短路径问题, 本例中使用的 Bellman-Ford 算法能够处理有负权重的问题,它的思想是通过对每一条边进行松弛化,重复遍历 V-1 次所有边
 * (V = 节点数)。
 * 这里的松弛化过程就是:我们用 d 来描述从源点出发到某一节点的最短路径估计,当我们要对一条 s->v 的边进行松弛化时,我们需要
 * 比较 v.d 和 s.d + w<s, v>,如果 v.d 更大则说明如果想要到哒 v 通过 s 再到 v 能更短,这时 s 将成为 v 的前缀路径,v 的 d 修改为
 * s.d + w<s, v>
 * 因为每次对所有边进行松弛化后,总会有至少一个节点的 d 达到最优值,所以重复遍历 V-1 次所有边之后,就能得到最终结果
 *
 * @author bbm
 */
public class BellmanFord {

    public static class Node {
        String name;
        int d = 10000;
        Node pre = null;

        public Node(String name) {
            this.name = name;
        }
    }

    public static class Line {
        int w;
        Node[] points;

        public Line(Node node1, Node node2, int w) {
            this.points = new Node[] {node1, node2};
            this.w = w;
        }
    }

    private static void relax(Node s, Node v, int w) {
        if (v.d > s.d + w) {
            v.d = s.d + w;
            v.pre = s;
        }
    }

    public static void main(String[] args) {
        Node s = new Node("s");
        Node t = new Node("t");
        Node x = new Node("x");
        Node z = new Node("z");
        Node y = new Node("y");
        List<Node> nodes = Arrays.asList(s, t, x, z, y);
        List<Line> lines = new LinkedList<>();
        lines.add(new Line(s, t, 6));
        lines.add(new Line(s, y, 7));
        lines.add(new Line(y, y, 8));
        lines.add(new Line(z, s, 2));
        lines.add(new Line(t, x, 5));
        lines.add(new Line(x, t, -2));
        lines.add(new Line(t, z, -4));
        lines.add(new Line(y, x, -3));
        lines.add(new Line(y, z, 9));
        lines.add(new Line(z, x, 7));

        s.d = 0;
        for (int i = 0; i < nodes.size() - 1; i++) {
            for (Line line : lines) {
                // 松弛化过程
                relax(line.points[0], line.points[1], line.w);
            }
        }
        for (Line line : lines) {
            if (line.points[1].d > line.points[0].d + line.w) {
                System.out.println("Has cycle");
            }
        }
        for (Node node : nodes) {
            System.out.println(node.name + ":" + node.d + "   pre:" + (node.pre != null ? node.pre.name : ""));
        }
    }
}

无环图单源最短路径

package bbm.graph;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import static bbm.graph.ShortestPathWithoutCycle.Color.GRAY;
import static bbm.graph.ShortestPathWithoutCycle.Color.WHITE;

/**
 * 解决无环图单源最短路径
 *
 * 在无环图中,我们可以按照拓扑排序的节点顺序,依次处理各个节点的所有边,当处理完最后一个节点时,整个图中的所有节点的最短路径就确定下来了
 *
 * 由于拓扑排序的性质,当在图中不存在从 B 到 A 的路径,则 A 在序列中排在 B 的前面,每当我们按照拓扑排序处理一个节点的所有边时,就会有至少一个
 * 节点的最短路径确定下来
 *
 * @author bbm
 */
public class ShortestPathWithoutCycle {
    public static class Node {
        String name;
        Color color = WHITE;
        int startTime;
        int endTime;
        Node pre = null;
        int d = 10000;
        List<Next> next = new LinkedList<>();

        public Node(String name) {
            this.name = name;
        }
    }

    public static class Next {
        Node node;
        int w;

        public Next(Node node, int w) {
            this.node = node;
            this.w = w;
        }
    }

    public enum Color {
        WHITE,
        GRAY,
        BLACK
    }

    public static int time = 0;

    public static void dfs(List<Node> graph) {
        graph.forEach(node -> {
            if (node.color.equals(WHITE)) {
                visit(node);
            }
        });
    }

    private static void visit(Node node) {
        time += 1;
        node.startTime = time;
        node.color = GRAY;
        node.next.forEach(next -> {
            if (next.node.color.equals(WHITE)) {
                visit(next.node);
            }
        });
        node.color = Color.BLACK;
        time += 1;
        node.endTime = time;
    }

    public static void topologicalSort(List<Node> nodes) {
        dfs(nodes);
        nodes.sort((o1, o2) -> o2.endTime - o1.endTime);
        for (Node node : nodes) {
            System.out.print(node.name + " ");
        }
    }

    private static void relax(Node s, Node v, int w) {
        if (v.d > s.d + w) {
            v.d = s.d + w;
            v.pre = s;
        }
    }

    public static void main(String[] args) {
        Node s = new Node("s");
        Node r = new Node("r");
        Node t = new Node("t");
        Node x = new Node("x");
        Node y = new Node("y");
        Node z = new Node("z");
        r.next.add(new Next(s, 5));
        r.next.add(new Next(t, 3));
        s.next.add(new Next(t, 2));
        s.next.add(new Next(x, 6));
        t.next.add(new Next(x, 7));
        t.next.add(new Next(y, 4));
        t.next.add(new Next(z, 2));
        x.next.add(new Next(z, 1));
        x.next.add(new Next(y, -1));
        y.next.add(new Next(z, -2));
        List<Node> nodes = Arrays.asList(s, r, z, y, x, t);
        topologicalSort(nodes);
        System.out.println();
        s.d = 0;
        for (Node node : nodes) {
            if (!node.next.isEmpty()) {
                for (Next next : node.next) {
                    relax(node, next.node, next.w);
                }
            }
        }
        for (Node node : nodes) {
            System.out.println(node.name + ":" + node.d + "   pre:" + (node.pre != null ? node.pre.name : ""));
        }
    }
}

Dijkstra

package bbm.graph;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

/**
 * 解决非负值权重的单源最短路径
 *
 * Dijkstra 将节点分为两个集合,一个是处理完的节点集合,一个时待处理集合,每次从待处理集合中选取 d 最小的节点,然后松弛化该节点的所有边,
 * 重复该过程直到处理集合为空
 *
 * @author bbm
 */
public class Dijkstra {
    public static class Node {
        String name;
        int d = 10000;
        Node pre = null;
        List<Next> next = new LinkedList<>();

        public Node(String name) {
            this.name = name;
        }
    }

    public static class Next {
        Node node;
        int w;

        public Next(Node node, int w) {
            this.node = node;
            this.w = w;
        }
    }

    private static void relax(Node s, Node v, int w) {
        if (v.d > s.d + w) {
            v.d = s.d + w;
            v.pre = s;
        }
    }

    public static void main(String[] args) {
        Node s = new Node("s");
        Node t = new Node("t");
        Node x = new Node("x");
        Node y = new Node("y");
        Node z = new Node("z");
        s.next.add(new Next(t, 10));
        s.next.add(new Next(y, 5));

        t.next.add(new Next(y, 2));
        t.next.add(new Next(x, 1));

        y.next.add(new Next(t, 3));
        y.next.add(new Next(x, 9));
        y.next.add(new Next(z, 2));

        x.next.add(new Next(z, 4));

        z.next.add(new Next(x, 6));
        z.next.add(new Next(s, 7));

        s.d = 0;
        List<Node> nodes = new ArrayList<>(Arrays.asList(s, t, x, y, z));
        List<Node> result = new ArrayList<>();
        while (!nodes.isEmpty()) {
            nodes.sort(Comparator.comparingInt(o -> o.d));
            Node node = nodes.remove(0);
            result.add(node);
            for (Next next: node.next) {
                relax(node, next.node, next.w);
            }
        }
        for (Node node: result) {
            System.out.println(node.name + ":" + node.d + "   pre:" + (node.pre != null ? node.pre.name : ""));
        }
    }
}

Floyd-Warshall

package bbm.graph;

/**
 * 解决所有节点对的最短路径问题,要求不存在负权重环路
 *
 * 假设图的所有节点为 1-n, 现在我们只考虑其中一个子集,1-k,这时候我们假设对于任何节点 i 和 j 它们的路径 p 属于 1-k,并且 p 为权重最小的路径
 * 本算法利用了 i 到 j 的中间节点均取自 1-(k-1) 的路径 p' 和上述路径 p 的关系来实现。现在我们有两条路径,其中 p 的中间节点来自于 1-k,
 * p' 的中间结点来自于 1-(k-1),要认清它们之间的关系,我们需要分两种情况:
 * 1. k 不是路径 p 的中间节点,也就是说路径 p 实际上中间结点只在 1-(k-1) 中,也就是 p = p'
 * 2. k 是路径 p 的中间结点,那么路径可以分解为 i -> k -> j,其中 ik 和 kj 路径中的节点均来自于 1-(k-1) ,因为我们要求图中没有负环路,
 * 这时候 p = ik + kj
 *
 *
 * @author bbm
 */
public class FloydWarshall {

    private static void floyd(int[][] weight) {
        int[][][] d = new int[6][5][5];
        Integer[][][] pre = new Integer[6][5][5];
        d[0] = weight;
        pre[0] = new Integer[5][5];
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j < weight.length; j++) {
                if (i == j || weight[i][j] == 10000) {
                    pre[0][i][j] = null;
                } else {
                    pre[0][i][j] = i + 1;
                }
            }
        }
        System.out.println("Pre" + 0 + ":");
        for (int i = 0; i < pre[0].length; i++) {
            for (int j = 0; j < pre[0].length; j++) {
                System.out.print(pre[0][i][j] + " ");
            }
            System.out.println();
        }
        for (int k = 1; k <= weight.length; k++) {
            d[k] = new int[5][5];
            pre[k] = new Integer[5][5];
            for (int i = 0; i < d[k].length; i++) {
                for (int j = 0; j < d[k].length; j++) {
                    d[k][i][j] = Math.min(d[k - 1][i][j], d[k - 1][i][k - 1] + d[k - 1][k - 1][j]);
                    if (d[k - 1][i][j] <= (d[k - 1][i][k - 1] + d[k - 1][k - 1][j])) {
                        pre[k][i][j] = pre[k-1][i][j];
                    } else {
                        pre[k][i][j] = pre[k-1][k-1][j];
                    }
                }
            }
            System.out.println("D" + k + ":");
            for (int i = 0; i < d[k].length; i++) {
                for (int j = 0; j < d[k].length; j++) {
                    System.out.print(d[k][i][j] + " ");
                }
                System.out.println();
            }
            System.out.println("Pre" + k + ":");
            for (int i = 0; i < pre[k].length; i++) {
                for (int j = 0; j < pre[k].length; j++) {
                    System.out.print(pre[k][i][j] + " ");
                }
                System.out.println();
            }
        }
    }

    public static void main(String[] args) {
        int[][] weight = new int[][] {
            {0, 3, 8, 10000, -4},
            {10000, 0, 10000, 1, 7},
            {10000, 4, 0, 10000, 10000},
            {2, 10000, -5, 0, 10000},
            {10000, 10000, 10000, 6, 0}
        };
        System.out.println("D" + 0 + ":");
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j < weight.length; j++) {
                System.out.print(weight[i][j] + " ");
            }
            System.out.println();
        }
        floyd(weight);
    }
}

Johnson

package bbm.graph;

import java.util.HashSet;
import java.util.Set;

/**
 * Johnson 算法主要用于求稀疏图上的所有节点对的最短路径。其主体思想是利用重新赋予权值的方法把一个原问题带负权的图转化为权值非负的图。
 * 然后再对每个节点使用一次 Dijkstra 算法以求出所有节点对的最短路径。
 *
 * @author bbm
 */
public class Johnson {

    private static int[] bellmanFord(int[][] newWeight) {
        int[] h = new int[newWeight.length];
        h[0] = 0;
        for (int i = 1; i < h.length; i++) {
            h[i] = 10000;
        }
        for (int k = 0; k < newWeight.length - 1; k++) {
            for (int i = 0; i < newWeight.length; i++) {
                for (int j = 0; j < newWeight.length; j++) {
                    h[j] = relax(h[j], h[i], newWeight[i][j]);
                }
            }
        }
        for (int i = 0; i < newWeight.length; i++) {
            for (int j = 0; j < newWeight.length; j++) {
                if (h[i] + newWeight[i][j] < h[j]) {
                    throw new RuntimeException("Has cycle");
                }
            }
        }
        return h;
    }

    private static int relax(int dv, int ds, int w) {
        if (dv > ds + w) {
            return ds + w;
        } else {
            return dv;
        }
    }

    private static void dijkstra(int index, int[][] weight) {
        System.out.println("Start from " + (index + 1));
        int[] length = new int[weight.length];
        length[index] = 0;
        for (int i = 0; i < length.length; i++) {
            if (i != index) {
                length[i] = 10000;
            }
        }
        Set<Integer> handled = new HashSet<>();
        while (handled.size() < weight.length) {
            int min = 10000;
            int minIndex = -1;
            for (int i = 0; i < length.length; i++) {
                if (!handled.contains(i) && length[i] < min) {
                    min = length[i];
                    minIndex = i;
                }
            }
            handled.add(minIndex);
            for (int i = 0; i < weight.length; i++) {
                if (weight[minIndex][i] != 10000 && i != minIndex) {
                    length[i] = relax(length[i], length[minIndex], weight[minIndex][i]);
                }
            }
        }
        for (int i = 0; i < length.length; i++) {
            System.out.print(length[i] + " ");
        }
        System.out.println();
    }

    private static void johnson(int[][] weight) {
        int[][] newWeight = new int[weight.length + 1][weight.length + 1];
        for (int i = 0; i < newWeight.length; i++) {
            newWeight[0][i] = 0;
        }
        for (int i = 1; i < newWeight.length; i++) {
            newWeight[i][0] = 10000;
        }
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j < weight.length; j++) {
                newWeight[i + 1][j + 1] = weight[i][j];
            }
        }
        System.out.println("New weight:");
        for (int i = 0; i < newWeight.length; i++) {
            for (int j = 0; j < newWeight.length; j++) {
                System.out.print(newWeight[i][j] + " ");
            }
            System.out.println();
        }
        int[] h = bellmanFord(newWeight);
        System.out.println("H:");
        for (int i = 0; i < h.length; i++) {
            System.out.print(h[i] + " ");
        }
        System.out.println();
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j < weight.length; j++) {
                if (weight[i][j] != 10000) {
                    weight[i][j] += h[i+1] - h[j+1];
                }
            }
        }
        System.out.println("Weight:");
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j < weight.length; j++) {
                System.out.print(weight[i][j] + " ");
            }
            System.out.println();
        }
        for (int i = 0; i < weight.length; i++) {
            dijkstra(i, weight);
        }
    }

    public static void main(String[] args) {
        int[][] weight = new int[][] {
            {0, 3, 8, 10000, -4},
            {10000, 0, 10000, 1, 7},
            {10000, 4, 0, 10000, 10000},
            {2, 10000, -5, 0, 10000},
            {10000, 10000, 10000, 6, 0}
        };
        System.out.println("Weight:");
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j < weight.length; j++) {
                System.out.print(weight[i][j] + " ");
            }
            System.out.println();
        }
        johnson(weight);
    }
}

参考内容

[1] 《算法导论》

stun

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝克街的流浪猫

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值