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() 字符串化
  1. public class Edge implements Comparable<Edge>{
  2. private final int v;
  3. private final int w;
  4. private final double weight;
  5. public Edge(int v, int w, double weight){
  6. this.v = v;
  7. this.w = w;
  8. this.weight = weight;
  9. }
  10. public double weight(){ return weight;}
  11. public int either(){ return v; }//一个端点
  12. public int other(int vertex){//对侧端点
  13. if (vertex == v) return w;
  14. else if (vertex == w) return v;
  15. else throw new RuntimeException("Inconsistent edge");
  16. }
  17. public int compareTo(Edge that){//自然比较
  18. if (this.weight() < that.weight()) return -1;
  19. else if(this.weight() > that.weight()) return 1;
  20. else return 0;
  21. }
  22. //每条边输出格式:点-点 权重
  23. public String toString(){
  24. return String.format("%d-%d %.2f",v,w,weight);
  25. }
  26. }

加权无向图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() 字符串化
  1. import edu.princeton.cs.algs4.In;
  2. import edu.princeton.cs.algs4.StdOut;
  3. public class EdgeWeightedGraph {
  4. private final int V;//总点数
  5. private int E;//总边数
  6. private Bag<Edge>[] adj;//邻接表
  7. public EdgeWeightedGraph(int V){
  8. this.V = V;
  9. this.E = 0;
  10. adj = (Bag<Edge>[])new Bag[V];
  11. for (int v = 0; v < V; v++)
  12. adj[v] = new Bag<>();
  13. }
  14. public EdgeWeightedGraph(In in){
  15. this(in.readInt());
  16. int E = in.readInt();
  17. for (int i = 0; i < E; i++)
  18. addEdge(new Edge(in.readInt(),in.readInt(),in.readDouble()));
  19. }
  20. public void addEdge(Edge e){
  21. int v = e.either(), w = e.other(v);
  22. adj[v].add(e);
  23. adj[w].add(e);
  24. E++;
  25. }
  26. public int V(){ return V;}
  27. public int E(){ return E;}
  28. //与v关联边
  29. public Iterable<Edge> adj(int v){return adj[v];}
  30. //所有边
  31. public Iterable<Edge> edges(){
  32. Bag<Edge> b = new Bag<>();
  33. for (int v = 0; v < V; v++)
  34. for (Edge e: adj(v))
  35. //忽略自环
  36. if (e.other(v) > v) b.add(e);
  37. return b;
  38. }
  39. public String toString(){
  40. String s = V + " vertices, " + E + " edges\n";
  41. for (int v = 0; v < V; v++){
  42. s += v + ": ";
  43. for (Edge e:adj(v))
  44. s += e + " ";
  45. s += "\n";
  46. }
  47. return s;
  48. }
  49. public static void main(String[] args){
  50. In in = new In(args[0]);
  51. EdgeWeightedGraph G;
  52. G = new EdgeWeightedGraph(in);
  53. StdOut.print(G);
  54. }
  55. }
  • 每条边在表中出现两次:和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 |