最小生成树
定义:图的生成树是它的一棵含有其所有顶点的无环连通子图。一幅加权图的最小生成树(MST)是它的一棵权值(树中所有边的权值之和)最小的生成树。
证明最小生成树的基本性质:
- 用一条边连接树中的任意两个顶点都会产生一个新的环;
- 从树中删去一条边将会得到两棵独立的树。
切分定理
定义:图的一种切分是将图的所有顶点分为两个非空且不重叠的两个集合。横切边是一条连接两个属于不同集合的顶点的边。
通常,我们通过指定一个顶点集并隐式地认为它的补集为另一个顶点集来指定一个切分。这样,一条横切边就是连接该集合的一个顶点和不在该集合中的另一个顶点的一条边。
切分定理:在一幅加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图的最小生成树。
贪心算法
切分定理是解决最小生成树问题的所有算法的基础。
最小生成树的贪心算法:下面这种方法会将含有V个顶点的任意加权连通图中属于最小生成树的边标记为黑色:初始状态下所有边均为灰色,找到一种切分,它产生的横切边均不为黑色。将它权重最小的横切边标记为黑色。反复,直到标记了V-1条黑色边为止。
Prim算法
Prim算法能够得到任意加权连通图的最小生成树。
Prim算法的每一步都会为一棵生长中的树添加一条边。一开始这棵树只有一个顶点,然后会向它添加V-1条边,每次总是将下一条连接树中的顶点与不在树中的顶点且权重最小的边加入树中。
每当我们向树中添加了一条边之后,也向树中添加了一个顶点。要维护一个包含所有横切边的集合,就要将连接这个顶点和其他所有不在树中的顶点的边加入优先队列。但还有一点:连接新加入树中的顶点与其他已经在树中顶点的所有边都失效了。(这样的边都已经不是横切边了,因为它的两个顶点都在树中。)Prim算法的即时实现可以将这样的边从优先队列中删掉,但我们先来学习这个算法的一种延时实现,将这些边先留在优先队列中,等到要删除它们的时候再检查边的有效性。
Prim算法轨迹:
算法性能
Prim算法的延时实现计算一幅含有V个顶点和E条边的连通加权无向图的最小生成树所需的空间与E成正比,所需时间与ElogE成正比(最坏情况)。
代码
Edge.java
package section4_3;
public class Edge implements Comparable<Edge> {
private final int v;
private final int w;
private final double weight;
public 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 vertex) {
if (vertex == v) return w;
else return v;
}
@Override
public int compareTo(Edge that) {
if (this.weight() < that.weight()) return -1;
else if (this.weight() > that.weight()) return 1;
else return 0;
}
public String toString() {
return String.format("%d-%d %.2f",v,w,weight);
}
}
EdgeWeightedGraph.java
package section4_3;
import section1_3.Bag;
public class EdgeWeightedGraph {
private final int V;
private int E;
private Bag<Edge>[] adj;
public EdgeWeightedGraph(int V) {
this.V = V;
this.E = 0;
adj = (Bag<Edge>[]) new Bag[V];
for (int v = 0;v < V;v++) {
adj[v] = new Bag<>();
}
}
public EdgeWeightedGraph(int vn, int en, int[][] data, double[] weights) {
this(vn);
for (int i = 0;i < en;i++) {
Edge edge = new Edge(data[i][0],data[i][1],weights[i]);
addEdge(edge);
}
}
public int V() {
return V;
}
public int E() {
return E;
}
public void addEdge(Edge e) {
int v = e.either();
int w = e.other(v);
adj[v].add(e);
adj[w].add(e);
E++;
}
public Iterable<Edge> adj(int v) {
return adj[v];
}
public Iterable<Edge> edges() {
Bag<Edge> b = new Bag<>();
for (int v = 0;v < V;v++) {
for (Edge e : adj[v]) {
if (e.other(v) > v) b.add(e);
}
}
return b;
}
}
MinPQ.java
package section2_1.priorityqueue;
public class MinPQ<key extends Comparable<key>> {
private key[] array;
private int idx;
public MinPQ(int max) {
array = (key[]) new Comparable[max];
idx = 0;
}
public void insert(key v) {
array[idx++] = v;
}
public key delMin() {
int min = 0;
for (int i=1;i < idx;i++) {
if (array[i].compareTo(array[min]) < 0) min = i;
}
idx--;
key temp = array[min];
array[min] = array[idx];
array[idx] = temp;
return temp;
}
public boolean isEmpty() {
return idx == 0;
}
int size() {
return idx;
}
}
LazyPrimMST.java
package section4_3;
import section2_1.priorityqueue.MinPQ;
import java.util.LinkedList;
import java.util.Queue;
public class LazyPrimMST {
private double weight;
private boolean[] marked;
private Queue<Edge> mst;
private MinPQ<Edge> pq;
public LazyPrimMST(EdgeWeightedGraph G) {
marked = new boolean[G.V()];
mst = new LinkedList<>();
pq = new MinPQ<>(50);
visit(G,0);
while (!pq.isEmpty()) {
Edge e = pq.delMin();
int v = e.either();
int w = e.other(v);
if (marked[v] && marked[w]) {
continue;
}
mst.offer(e);
weight += e.weight();
if (!marked[v]) visit(G,v);
if (!marked[w]) visit(G,w);
}
}
private void visit(EdgeWeightedGraph G, int v) {
marked[v] = true;
for (Edge e : G.adj(v)) {
if (!marked[e.other(v)]) {
pq.insert(e);
}
}
}
public Iterable<Edge> edges() {
return mst;
}
public double weight() {
return weight;
}
public static void main(String[] args) {
int[][] data = {
{4,5},
{4,7},
{5,7},
{0,7},
{1,5},
{0,4},
{2,3},
{1,7},
{0,2},
{1,2},
{1,3},
{2,7},
{6,2},
{3,6},
{6,0},
{6,4}
};
double[] weights = {
.35,
.37,
.28,
.16,
.32,
.38,
.17,
.19,
.26,
.36,
.29,
.34,
.40,
.52,
.58,
.93
};
int vn = 8;
int en = 16;
EdgeWeightedGraph G = new EdgeWeightedGraph(vn,en,data,weights);
LazyPrimMST mst = new LazyPrimMST(G);
for (Edge e : mst.edges()) {
System.out.println(e);
}
System.out.println(mst.weight());
}
}
输出: