跳转至

最小生成树

最小生成树(Minimum spanning tree,MST)是最小权重生成树(Minimum weight spanning tree)的简称,是一副连通加权无向图中一棵权值最小的生成树。

Prim

贪心算法。每次从图中找到离生成树最近的结点,然后加进生成树中。一开始,生成树中只有一个起点。

Lazy

public class LazyPrimMST {
    private Queue<Edge> mst;     // edges in the MST
    private boolean[] marked;    // marked[v] = true iff v on tree
    private MinPQ<Edge> pq;      // edges with one endpoint in tree

    public LazyPrimMST(EdgeWeightedGraph G) {
        mst = new Queue<Edge>();
        pq = new MinPQ<Edge>();
        marked = new boolean[G.V()];

        for (int v = 0; v < G.V(); v++)  // run Prim from all vertices to
            if (!marked[v]) prim(G, v);  // get a minimum spanning forest
    }

    // run Prim's algorithm
    private void prim(EdgeWeightedGraph G, int s) {
        scan(G, s);
        while (!pq.isEmpty()) {                  // better to stop when mst has V-1 edges
            Edge e = pq.delMin();                // smallest edge on pq
            int v = e.either(), w = e.other(v);  // two endpoints

            // lazy, both v and w already scanned
            if (marked[v] && marked[w])
                continue;

            mst.enqueue(e);              // add e to MST
            if (!marked[v]) scan(G, v);  // v becomes part of tree
            if (!marked[w]) scan(G, w);  // w becomes part of tree
        }
    }

    // add all edges e incident to v onto pq
    // if the other endpoint has not yet been scanned
    private void scan(EdgeWeightedGraph G, int v) {
        marked[v] = true;
        for (Edge e : G.adj(v))
            if (!marked[e.other(v)]) pq.insert(e);
    }
}
  • 时间复杂度:\(O(E \log E)\)
  • 空间复杂度:\(O(E+V)\)

Eager

需要用到 IndexMinPQ,有类似 Dijkstra 的松弛操作。这种实现能直接修改优先队列中元素的优先级。Lazy 实现不能直接修改元素的优先级,而是用新元素「插队」旧元素,所以队列中有很多无效的旧元素。

public class PrimMST {
    private Edge[] edgeTo;     // edgeTo[v] = shortest edge from tree vertex to non-tree vertex
    private double[] distTo;   // distTo[v] = weight of shortest such edge
    private boolean[] marked;  // marked[v] = true if v on tree, false otherwise
    private IndexMinPQ<Double> pq;

    public PrimMST(EdgeWeightedGraph G) {
        edgeTo = new Edge[G.V()];
        distTo = new double[G.V()];
        marked = new boolean[G.V()];
        pq = new IndexMinPQ<Double>(G.V());
        for (int v = 0; v < G.V(); v++)
            distTo[v] = Double.POSITIVE_INFINITY;

        for (int v = 0; v < G.V(); v++)  // run from each vertex to find
            if (!marked[v]) prim(G, v);  // minimum spanning forest
    }

    // run Prim's algorithm in graph G, starting from vertex s
    private void prim(EdgeWeightedGraph G, int s) {
        distTo[s] = 0.0;
        pq.insert(s, distTo[s]);
        while (!pq.isEmpty()) {
            int v = pq.delMin();
            scan(G, v);
        }
    }

    // scan vertex v
    private void scan(EdgeWeightedGraph G, int v) {
        marked[v] = true;
        for (Edge e : G.adj(v)) {
            int w = e.other(v);
            if (marked[w]) continue; // v-w is obsolete edge
            if (e.weight() < distTo[w]) {
                distTo[w] = e.weight();
                edgeTo[w] = e;
                if (pq.contains(w)) pq.decreaseKey(w, distTo[w]);
                else                pq.insert(w, distTo[w]);
            }
        }
    }
}
  • 时间复杂度:\(O(E \log V)\)
  • 空间复杂度:\(O(V)\)

Kruskal

贪心算法。

根据权值从小到大加入边,如果加入这条边会产生环,则丢弃这条边。

可以用优先队列(或数组 + 排序)来存边,用并查集来维护两节点的连通关系。如果某条边的两节点之前就已经连通,那么加入这条边就会产生环,需要丢弃这条边。

public class KruskalMST {
    private Queue<Edge> mst = new Queue<Edge>();  // edges in MST

    public KruskalMST(EdgeWeightedGraph G) {
        // create array of edges, sorted by weight
        Edge[] edges = new Edge[G.E()];
        int t = 0;
        for (Edge e: G.edges()) {
            edges[t++] = e;
        }
        Arrays.sort(edges);

        // run greedy algorithm
        UF uf = new UF(G.V());
        for (int i = 0; i < G.E() && mst.size() < G.V() - 1; i++) {
            Edge e = edges[i];
            int v = e.either();
            int w = e.other(v);

            // v-w does not create a cycle
            if (uf.find(v) != uf.find(w)) {
                uf.union(v, w);     // merge v and w components
                mst.enqueue(e);     // add edge e to mst
            }
        }
    }
}
  • 时间复杂度 \(O(E \log E)\)
  • 空间复杂度 \(O(E+V)\)