最小生成树¶
最小生成树(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)\)