跳转至

最短路径

BFS

可以找到边数最少的路径,不考虑权值。

Dijkstra

Dijkstra(/ˈdikstrɑ/或/ˈdɛikstrɑ/)算法由荷兰计算机科学家 E. W. Dijkstra 于 1956 年发现,1959 年公开发表。是一种求解非负权图上单源最短路径的算法。

松弛操作使用 IndexMinPQ;也可以用普通的优先队列 + vis 数组,新值直接插入队列即可,靠 vis 数组保证每个结点只访问一次。

public class DijkstraSP {
    private double[] distTo;       // distTo[v] = distance  of shortest s->v path
    private DirectedEdge[] edgeTo; // edgeTo[v] = last edge on shortest s->v path
    private IndexMinPQ<Double> pq; // priority queue of vertices

    public DijkstraSP(EdgeWeightedDigraph G, int s) {
        for (DirectedEdge e : G.edges()) {
            if (e.weight() < 0)
                throw new IllegalArgumentException("edge " + e + " has negative weight");
        }

        distTo = new double[G.V()];
        edgeTo = new DirectedEdge[G.V()];

        for (int v = 0; v < G.V(); v++)
            distTo[v] = Double.POSITIVE_INFINITY;
        distTo[s] = 0.0;

        // relax vertices in order of distance from s
        pq = new IndexMinPQ<Double>(G.V());
        pq.insert(s, distTo[s]);
        while (!pq.isEmpty()) {
            int v = pq.delMin();
            for (DirectedEdge e : G.adj(v))
                relax(e);
        }
    }

    // relax edge e and update pq if changed
    private void relax(DirectedEdge e) {
        int v = e.from(), w = e.to();
        if (distTo[w] > distTo[v] + e.weight()) {
            distTo[w] = distTo[v] + e.weight();
            edgeTo[w] = e;
            if (pq.contains(w)) pq.decreaseKey(w, distTo[w]);
            else                pq.insert(w, distTo[w]);
        }
    }

    public Iterable<DirectedEdge> pathTo(int v) {
        Stack<DirectedEdge> path = new Stack<DirectedEdge>();
        for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) {
            path.push(e);
        }
        return path;
    }
}
  • 时间复杂度:\(O(E \log V)\)
  • 空间复杂度:\(O(V)\)

Bellman-Ford

Bellman–Ford 算法是一种基于松弛(relax)操作的最短路算法,可以求出有负权的图的最短路,并可以对最短路不存在的情况进行判断。

从起点开始,松弛一个顶点后,继续松弛它周围的顶点,直到所有点都不能再松弛。如果图中有负环的话,每绕一圈总路径长度都会变小,就无限循环了。如果图的顶点数为 V,路径长度最大就 V-1(所有点都经过一次),使用 cnt[v] 记录从起点到 v 经过的边数,如果大于 V-1 说明有负环。

public class BellmanFordSP {
    private double[] distTo;       // distTo[v] = distance  of shortest s->v path
    private DirectedEdge[] edgeTo; // edgeTo[v] = last edge on shortest s->v path
    private boolean[] onQueue;     // onQueue[v] = is v currently on the queue?
    private Queue<Integer> queue;  // queue of vertices to relax
    private int[] cnt;             // cnt[v] = number of edges from the source vertex to v
    private boolean hasNegativeCycle;

    public BellmanFordSP(EdgeWeightedDigraph G, int s) {
        distTo  = new double[G.V()];
        edgeTo  = new DirectedEdge[G.V()];
        onQueue = new boolean[G.V()];
        cnt     = new int[G.V()];
        for (int v = 0; v < G.V(); v++)
            distTo[v] = Double.POSITIVE_INFINITY;
        distTo[s] = 0.0;

        // Bellman-Ford algorithm
        queue = new Queue<Integer>();
        queue.enqueue(s);
        onQueue[s] = true;
        while (!queue.isEmpty() && !hasNegativeCycle) {
            int v = queue.dequeue();
            onQueue[v] = false;
            relax(G, v);
        }
    }

    // relax vertex v and put other endpoints on queue if changed
    private void relax(EdgeWeightedDigraph G, int v) {
        for (DirectedEdge e : G.adj(v)) {
            int w = e.to();
            if (distTo[w] > distTo[v] + e.weight()) {
                distTo[w] = distTo[v] + e.weight();
                edgeTo[w] = e;
                cnt[w] = cnt[v] + 1;
                if (cnt[w] >= G.V()) {
                    hasNegativeCycle = true;
                    return;
                }
                if (!onQueue[w]) {
                    queue.enqueue(w);
                    onQueue[w] = true;
                }
            }
        }
    }

    public Iterable<DirectedEdge> pathTo(int v) {
        if (hasNegativeCycle)
            throw new UnsupportedOperationException("Negative cost cycle exists");
        Stack<DirectedEdge> path = new Stack<DirectedEdge>();
        for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) {
            path.push(e);
        }
        return path;
    }
}
  • 通常情况下,每个边仅被松弛一次,时间复杂度:\(O(V+E)\)
  • 最坏时间复杂度:\(O(VE)\)
  • 空间复杂度:\(O(V)\)

相关文章