4.3 最小生成树
定义:图的生成树是含有所有顶点的无环连通子图,加权图的最小生成树(MST)是一棵权值最小的生成树
约定
- 只限于连通图:由定义,最小生成树只存在于连通图
 - 权值可为0或者负数
 - 所有边的权重都不同,否则MST不唯一
 
4.3.1 原理
4.3.3.1 切分定理
切分:即把图所有顶点分成两个非空且不重叠的部分
横切边即连接这两部分,两端各属于不同部分的边
切分定理:加权图中,给定任意的切分,其横切边中权重最小者一定属于图的最小生成树
证明:反证法:设T为图的最小生成树,e为权重最小的横切边且不在T中,由树的性质,T中加入一条边必定成环,则连接e,此时环中至少含有另一条横切边f,而f权重大于e,则删去f保留e,的到更小的树,与前提矛盾
4.3.3.2 最小生成树的贪心算法
算法:使用切分定理:V个顶点的任意加权连通图中:在一种切分下,选出权重最小横切边,一定含于MST中;另一种切分下,同样操作;最终当选出V-1条不同的横切边时,即为MST
4.3.2 加权无向图的数据类型:加权边Edge->加权无向图EdgeWeightedGraph
加权边API:public class Edge implements Comparable
| 返回类型 | 方法 | 描述 | 
|---|---|---|
| Edge(int v, int w, double weight) | 构造函数 | |
| double | weight() | 边的权重 | 
| int | either() | 两端点之一 | 
| int | other() | 另一个端点 | 
| int | compareTo(Edge that) | 权重比较 | 
| String | toString() | 字符串化 | 
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 if (vertex == w) return v;else throw new RuntimeException("Inconsistent edge");}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);}}
加权无向图API:public class EdgeWeightedGraph
| 返回类型 | 方法 | 描述 | 
|---|---|---|
| EdgeWeightedGraph(int V) | 创建V结点的空图 | |
| EdgeWeightedGraph(In in) | 从输入流建图 | |
| int | V() | 顶点数 | 
| int | E() | 边数 | 
| void | addEdge(Edge e) | 添加边e | 
| Iterable | adj(int v) | 与v关联所有边 | 
| Iterable | edges() | 图中所有边 | 
| String | toString() | 字符串化 | 
import edu.princeton.cs.algs4.In;import edu.princeton.cs.algs4.StdOut;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(In in){this(in.readInt());int E = in.readInt();for (int i = 0; i < E; i++)addEdge(new Edge(in.readInt(),in.readInt(),in.readDouble()));}public void addEdge(Edge e){int v = e.either(), w = e.other(v);adj[v].add(e);adj[w].add(e);E++;}public int V(){ return V;}public int E(){ return E;}//与v关联边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;}public String toString(){String s = V + " vertices, " + E + " edges\n";for (int v = 0; v < V; v++){s += v + ": ";for (Edge e:adj(v))s += e + " ";s += "\n";}return s;}public static void main(String[] args){In in = new In(args[0]);EdgeWeightedGraph G;G = new EdgeWeightedGraph(in);StdOut.print(G);}}
- 每条边在表中出现两次:和Graph每个顶点出现两次一样,一个Edge在addEdge时,需要给adj[v]和adj[w]同时add
 - 平行边:EWG中运行保留权重不一样的平行边
 - 自环:EWG中运行存在自环,但edges()未考虑自环
 使用Edge辅助类的代价:
- 每个邻接表结点是指向Edge对象的引用,还要冗余信息:每个adj[v]都会保存v
 - 使用对象本身的开销
 - 一个Edge对象需要创建两个引用来指向
 
4.3.3 最小生成树API和用例(算法4.7)
4.3.4 Prim算法(算法4.7)
4.3.5 Prim算法即时实现(算法4.7)
4.3.6 Kruskal算法(算法4.8)
4.3.7 总结
- 最小生成树算法总结: | 算法 | 空间 | 时间 | | :—-: | :—-: | :—-: | | 延时Prim | E | ElogE | | 即时Prim | V | ElogV | | Kruskal | E | ElogE |
 
