一、思想
1.1 基本概念
- 加权无向图的生成树:一棵含有其所有顶点的无环连通子图。
- 最小生成树(MST):一棵权值最小(树中所有边的权值之和)的生成树。
1.2 算法原理
1.2.1 切分定理
- 切分定义:图的一种切分是将图的所有顶点分为两个非空且不重合的两个集合。横切边是一条连接两个属于不同集合的顶点的边。
- 切分定理:在一幅加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图的最小生成树。
1.2.2 算法原理
切分定理是解决最小生成树问题的所有算法的基础。切分定理再结合贪心算法思想,就可以最终落地实现最小生成树。
Prim算法原理:
一开始树中只有一个顶点,向它添加v-1条边,每次总是将下一条连接 “树中的顶点” 与 “不在树中的顶点” 且权重最小的边,加入树中。如下图,当我们将顶点v添加到树中时,可能使得w到最小生成树的距离更近了(然后遍历顶点v的领接链表即可)。
核心:
使用一个索引优先队列,保存每个非树顶点w的一条边(将它与树中顶点连接起来的权重最小的边)。优先队列(小顶堆)的最小键即是权重最小的横切边的权重,而和它相关联的顶点V就是下一个将被添加到树中的顶点。
二、实现
2.1 无向边
1 package study.algorithm.graph; 2 3 import study.algorithm.base.StdOut; 4 5 /*** 6 * @Description 无向边 7 * @author denny.zhang 8 * @date 2020/5/25 10:34 上午 9 */ 10 public class Edge implements Comparable<Edge> { 11 12 /** 13 * 一个顶点 14 */ 15 private final int v; 16 /** 17 * 另一个顶点 18 */ 19 private final int w; 20 /** 21 * 权重 22 */ 23 private final double weight; 24 25 /** 26 * Initializes an edge between vertices { @code v} and { @code w} of 27 * the given { @code weight}. 28 * 29 * @param v one vertex 30 * @param w the other vertex 31 * @param weight the weight of this edge 32 * @throws IllegalArgumentException if either { @code v} or { @code w} 33 * is a negative integer 34 * @throws IllegalArgumentException if { @code weight} is { @code NaN} 35 */ 36 public Edge(int v, int w, double weight) { 37 if (v < 0) throw new IllegalArgumentException("vertex index must be a nonnegative integer"); 38 if (w < 0) throw new IllegalArgumentException("vertex index must be a nonnegative integer"); 39 if (Double.isNaN(weight)) throw new IllegalArgumentException("Weight is NaN"); 40 this.v = v; 41 this.w = w; 42 this.weight = weight; 43 } 44 45 /** 46 * Returns the weight of this edge. 47 * 48 * @return the weight of this edge 49 */ 50 public double weight() { 51 return weight; 52 } 53 54 /** 55 * 返回边的任意一个顶点 56 * 57 * @return either endpoint of this edge 58 */ 59 public int either() { 60 return v; 61 } 62 63 /** 64 * 返回边的另一个顶点 65 * 66 * @param vertex one endpoint of this edge 67 * @return the other endpoint of this edge 68 * @throws IllegalArgumentException if the vertex is not one of the 69 * endpoints of this edge 70 */ 71 public int other(int vertex) { 72 if (vertex == v) return w; 73 else if (vertex == w) return v; 74 else throw new IllegalArgumentException("Illegal endpoint"); 75 } 76 77 /** 78 * Compares two edges by weight. 79 * Note that { @code compareTo()} is not consistent with { @code equals()}, 80 * which uses the reference equality implementation inherited from { @code Object}. 81 * 82 * @param that the other edge 83 * @return a negative integer, zero, or positive integer depending on whether 84 * the weight of this is less than, equal to, or greater than the 85 * argument edge 86 */ 87 @Override 88 public int compareTo(Edge that) { 89 return Double.compare(this.weight, that.weight); 90 } 91 92 /** 93 * Returns a string representation of this edge. 94 * 95 * @return a string representation of this edge 96 */ 97 public String toString() { 98 return String.format("%d-%d %.5f", v, w, weight); 99 } 100 101 /** 102 * Unit tests the { @code Edge} data type. 103 * 104 * @param args the command-line arguments 105 */ 106 public static void main(String[] args) { 107 Edge e = new Edge(12, 34, 5.67); 108 StdOut.println(e); 109 StdOut.println("任意一个顶点="+e.either()); 110 StdOut.println("另一个顶点="+e.other(12)); 111 } 112 }
如上图,初始化时构造了一个邻接表。Bag<Edge>[] adj,如下图。每条边有2个顶点,所以插入adj[]中2次。
2.2.边加权无向图
1 package study.algorithm.graph; 2 3 import study.algorithm.base.*; 4 5 import java.util.NoSuchElementException; 6 7 /*** 8 * @Description 边权重无向图 9 * @author denny.zhang 10 * @date 2020/5/25 10:50 上午 11 */ 12 public class EdgeWeightedGraph { 13 private static final String NEWLINE = System.getProperty("line.separator"); 14 15 /** 16 * 顶点数 17 */ 18 private final int V; 19 /** 20 * 边数 21 */ 22 private int E; 23 /** 24 * 顶点邻接表,每个元素Bag代表:由某个顶点关联的边数组,按顶点顺序排列 25 */ 26 private Bag<Edge>[] adj; 27 28 /** 29 * Initializes an empty edge-weighted graph with { @code V} vertices and 0 edges. 30 * 31 * @param V the number of vertices 32 * @throws IllegalArgumentException if { @code V < 0} 33 */ 34 public EdgeWeightedGraph(int V) { 35 if (V < 0) throw new IllegalArgumentException("Number of vertices must be nonnegative"); 36 this.V = V; 37 this.E = 0; 38 adj = (Bag<Edge>[]) new Bag[V]; 39 for (int v = 0; v < V; v++) { 40 adj[v] = new Bag<Edge>(); 41 } 42 } 43 44 /** 45 * Initializes a random edge-weighted graph with { @code V} vertices and <em>E</em> edges. 46 * 47 * @param V the number of vertices 48 * @param E the number of edges 49 * @throws IllegalArgumentException if { @code V < 0} 50 * @throws IllegalArgumentException if { @code E < 0} 51 */ 52 public EdgeWeightedGraph(int V, int E) { 53 this(V); 54 if (E < 0) throw new IllegalArgumentException("Number of edges must be nonnegative"); 55 for (int i = 0; i < E; i++) { 56 int v = StdRandom.uniform(V); 57 int w = StdRandom.uniform(V); 58 double weight = Math.round(100 * StdRandom.uniform()) / 100.0; 59 Edge e = new Edge(v, w, weight); 60 addEdge(e); 61 } 62 } 63 64 /** 65 * Initializes an edge-weighted graph from an input stream. 66 * The format is the number of vertices <em>V</em>, 67 * followed by the number of edges <em>E</em>, 68 * followed by <em>E</em> pairs of vertices and edge weights, 69 * with each entry separated by whitespace. 70 * 71 * @param in the input stream 72 * @throws IllegalArgumentException if { @code in} is { @code null} 73 * @throws IllegalArgumentException if the endpoints of any edge are not in prescribed range 74 * @throws IllegalArgumentException if the number of vertices or edges is negative 75 */ 76 public EdgeWeightedGraph(In in) { 77 if (in == null) throw new IllegalArgumentException("argument is null"); 78 79 try { 80 // 顶点数 81 V = in.readInt(); 82 // 邻接表 83 adj = (Bag<Edge>[]) new Bag[V]; 84 // 初始化邻接表,一个顶点对应一条链表 85 for (int v = 0; v < V; v++) { 86 adj[v] = new Bag<Edge>(); 87 } 88 // 边数 89 int E = in.readInt(); 90 if (E < 0) throw new IllegalArgumentException("Number of edges must be nonnegative"); 91 // 遍历每一条边 92 for (int i = 0; i < E; i++)