树的两条基本性质:
在一幅加权图中,给定任意的切分,它的横切边中的权重最小者必然属于树的最小生成树。
将含有V个顶点的任意加权连通图中属于最小生成树的边标记为黑色:初始状态下所有边均为灰色,找到一种切分,它产生的横切边均不为黑色。将它权重最小的横切边标记为黑色。反复,直到标记了V-1条黑色边为止。
public class Edge implements Comparable<Edge>{
private int v, w;
private double weight;
Edge(int v, int w, double weight){
this.v = v;
this.w = w;
this.weight = weight;
}
public double weight(){
return weight;
}
public int either(){
return v;
}
public int other(int v){
return this.v == v ? w : this.v;
}
@Override
public int compareTo(Edge o) {
return (int)Math.floor(weight - o.weight);// 注意浮点数的比较
}
}
public class EdgeWeightGraph {
private int V;
private int E;
private Bag<Edge>[] adj;
EdgeWeightGraph(int V){
adj = new Bag[V];
for(int i = 0; i < V; i++){
adj[i] = new Bag<Edge>();
}
}
public int V(){
return V;
}
public int E(){
return E;
}
public void addEdge(int v, int w, double weight){
adj[v].add(new Edge(v, w, weight));
E++;
}
Iterable<Edge> adj(int v){
return adj[v];
}
public EdgeWeightGraph reverse(){
EdgeWeightGraph copy = new EdgeWeightGraph(V);
for(int v = 0; v < V; v++){
for(Edge e : adj(v)){
copy.addEdge(e.other(v), v, e.weight());
}
}
return copy;
}
}
Prim算法的每一步都会为一棵生长中的树添加一条边。开始这棵树只有一个顶点,然后会向它添加V-1条边,每次总是将下一条连接树中的顶点与不在树中的顶点且权重最小的边加入树中(即由树中的顶点所定义的切分中的一条横切边)。
我们从优先队列中取出一条边并将它添加到树中(如果它还没有失效的话),再把这条边的另一个顶点也添加到树中,然后用新顶点作为参数调用visit()方法来更新横切边的集合。weight()方法可以遍历树的所有边并得到它们的权重之和(延时实现)或是用一个运行时的变量统计总权重(即时实现)。
import java.util.LinkedList;
import java.util.Queue;
public class LazyPrimMST {
private boolean[] marked;
private Queue<Edge> pq;
LazyPrimMST(EdgeWeightGraph G){
marked = new boolean[G.V()];
pq = new LinkedList<Edge>();
visit(G, 0);
while (!pq.isEmpty()){
Edge e = pq.poll();
int v = e.either(), w = e.other(v);
if(marked[v] && marked[w]) continue;
if(!marked[v]) visit(G, v);
if(!marked[w]) visit(G, w);
}
}
private void visit(EdgeWeightGraph G, int v){
marked[v] = true;
for(Edge e : G.adj(v)){
int w = e.other(v);
if(!marked[w]){
pq.add(e);
}
}
}
public Iterable<Edge> edges(){
return pq;
}
public double weight(){
double weight = 0;
for(Edge e : pq){
weight += e.weight();
}
return weight;
}
}
可以从如下两个方面改进LazyPrimMST:
PrimMST类将LazyPrimMST中的marked[]和mst[]替换为两个顶点索引的数组edgeTo[]和distTo[],它们具有如下性质:
import Sort.IndexMinPQ;
import java.util.LinkedList;
import java.util.Queue;
public class PrimMST {
private boolean[] marked;
private IndexMinPQ<Double> pq;
private Edge[] edgeTo;
private double[] distTo;
PrimMST(EdgeWeightGraph G){
int V = G.V();
marked = new boolean[V];
pq = new IndexMinPQ<Double>(V);
for(int v = 0; v < V; v++){
distTo[v] = Double.POSITIVE_INFINITY;
}
distTo[0] = 0.0;
pq.insert(0, 0.0);
visit(G, 0);
while (!pq.isEmpty()){
visit(G, pq.delMin()); // 将最近的顶点添加到树中
}
}
private void visit(EdgeWeightGraph G, int v){
marked[v] = true;
for(Edge e : G.adj(v)){
int w = e.other(v);
if(marked[w]) continue;
if(e.weight() < distTo[w]){
edgeTo[w] = e;
distTo[w] = e.weight();
if(pq.contain(w)) pq.change(w, distTo[w]);
else pq.insert(w, distTo[w]);
}
}
}
public Iterable<Edge> edges(){
Queue<Edge> mst = new LinkedList<Edge>();
for(Edge e : edgeTo) {
if (e != null) {
mst.add(e);
}
}
return mst;
}
public double weight(){
double weight = 0;
for(Edge e : edges()){
weight += e.weight();
}
return weight;
}
}
算法按照边的权重顺序(从小到大)处理它们,将边加入最小生成树中,加入的边不会与已经加入的边构成环,直到树中含有V-1条边为止。
我们使用一条优先队列来将边按照权重排序,用一个union-find数据结构来识别会形成环的边,以及一条队列来保存最小生成树的所有边。
package Graph;
import Sort.MinPQ;
import UnionFind.WeightedQuickUnionUF;
import java.util.LinkedList;
import java.util.Queue;
public class KruskalMST {
private boolean[] marked;
private Queue<Edge> mst;
KruskalMST(EdgeWeightGraph G){
marked = new boolean[G.V()];
mst = new LinkedList<Edge>();
MinPQ<Edge> pq = new MinPQ<Edge>();
for(Edge e:G.edges()) pq.insert(e);
WeightedQuickUnionUF uf = new WeightedQuickUnionUF(G.V());
while (!pq.isEmpty()){
Edge e = pq.delMin();
int v = e.either(), w = e.other(v);
if(uf.connected(v, w)) continue;
uf.union(v, w);
mst.add(e);
}
}
public Iterable<Edge> edges(){
return mst;
}
public double weight(){
double weight = 0;
for(Edge e : mst){
weight += e.weight();
}
return weight;
}
}