最小生成树(二)Prim算法
一、思想
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++) {
93 // 一个顶点
94 int v = in.readInt();
95 // 另一个顶点
96 int w = in.readInt();
97 validateVertex(v);
98 validateVertex(w);
99 // 权重
100 double weight = in.readDouble();
101 // 构造边
102 Edge e = new Edge(v, w, weight);
103 // 添加边
104 addEdge(e);
105 }
106 }
107 catch (NoSuchElementException e) {
108 throw new IllegalArgumentException("invalid input format in EdgeWeightedGraph constructor", e);
109 }
110
111 }
112
113 /**
114 * Initializes a new edge-weighted graph that is a deep copy of {@code G}.
115 *
116 * @param G the edge-weighted graph to copy
117 */
118 public EdgeWeightedGraph(EdgeWeightedGraph G) {
119 this(G.V());
120 this.E = G.E();
121 for (int v = 0; v < G.V(); v++) {
122 // reverse so that adjacency list is in same order as original
123 Stack<Edge> reverse = new Stack<Edge>();
124 for (Edge e : G.adj[v]) {
125 reverse.push(e);
126 }
127 for (Edge e : reverse) {
128 adj[v].add(e);
129 }
130 }
131 }
132
133
134 /**
135 * Returns the number of vertices in this edge-weighted graph.
136 *
137 * @return the number of vertices in this edge-weighted graph
138 */
139 public int V() {
140 return V;
141 }
142
143 /**
144 * Returns the number of edges in this edge-weighted graph.
145 *
146 * @return the number of edges in this edge-weighted graph
147 */
148 public int E() {
149 return E;
150 }
151
152 // throw an IllegalArgumentException unless {@code 0 <= v < V}
153 private void validateVertex(int v) {
154 if (v < 0 || v >= V)
155 throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
156 }
157
158 /**
159 * Adds the undirected edge {@code e} to this edge-weighted graph.
160 *
161 * @param e the edge
162 * @throws IllegalArgumentException unless both endpoints are between {@code 0} and {@code V-1}
163 */
164 public void addEdge(Edge e) {
165 // 一个顶点
166 int v = e.either();
167 // 另一个顶点
168 int w = e.other(v);
169 validateVertex(v);
170 validateVertex(w);
171 // 追加进顶点v的邻接链表
172 adj[v].add(e);
173 // 追加进顶点w的领接链表
174 adj[w].add(e);
175 // 边数++
176 E++;
177 }
178
179 /**
180 * 顶点V关联的全部边
181 *
182 * @param v the vertex
183 * @return the edges incident on vertex {@code v} as an Iterable
184 * @throws IllegalArgumentException unless {@code 0 <= v < V}
185 */
186 public Iterable<Edge> adj(int v) {
187 validateVertex(v);
188 return adj[v];
189 }
190
191 /**
192 * Returns the degree of vertex {@code v}.
193 *
194 * @param v the vertex
195 * @return the degree of vertex {@code v}
196 * @throws IllegalArgumentException unless {@code 0 <= v < V}
197 */
198 public int degree(int v) {
199 validateVertex(v);
200 return adj[v].size();
201 }
202
203 /**
204 * Returns all edges in this edge-weighted graph.
205 * To iterate over the edges in this edge-weighted graph, use foreach notation:
206 * {@code for (Edge e : G.edges())}.
207 *
208 * @return all edges in this edge-weighted graph, as an iterable
209 */
210 public Iterable<Edge> edges() {
211 Bag<Edge> list = new Bag<Edge>();
212 for (int v = 0; v < V; v++) {
213 int selfLoops = 0;
214 for (Edge e : adj(v)) {
215 if (e.other(v) > v) {
216 list.add(e);
217 }
218 // add only one copy of each self loop (self loops will be consecutive)
219 else if (e.other(v) == v) {
220 if (selfLoops % 2 == 0) list.add(e);
221 selfLoops++;
222 }
223 }
224 }
225 return list;
226 }
227
228 /**
229 * Returns a string representation of the edge-weighted graph.
230 * This method takes time proportional to <em>E</em> + <em>V</em>.
231 *
232 * @return the number of vertices <em>V</em>, followed by the number of edges <em>E</em>,
233 * followed by the <em>V</em> adjacency lists of edges
234 */
235 public String toString() {
236 StringBuilder s = new StringBuilder();
237 s.append(V + " " + E + NEWLINE);
238 for (int v = 0; v < V; v++) {
239 s.append(v + ": ");
240 for (Edge e : adj[v]) {
241 s.append(e + " ");
242 }
243 s.append(NEWLINE);
244 }
245 return s.toString();
246 }
247
248 /**
249 * Unit tests the {@code EdgeWeightedGraph} data type.
250 *
251 * @param args the command-line arguments
252 */
253 public static void main(String[] args) {
254 In in = new In(args[0]);
255 EdgeWeightedGraph G = new EdgeWeightedGraph(in);
256 StdOut.println(G);
257 }
258
259 }
2.3 索引小值优先队列
1 package study.algorithm.base;
2
3 import java.util.Iterator;
4 import java.util.NoSuchElementException;
5
6 /**
7 * 索引(顶点)最小优先级队列
8 *
9 * @param <Key>
10 */
11 public class IndexMinPQ<Key extends Comparable<Key>> implements Iterable<Integer> {
12 /**
13 * 元素数量上限
14 */
15 private int maxN;
16 /**
17 * 元素数量
18 */
19 private int n;
20 /**
21 * 索引二叉堆(数组中每个元素都是顶点,顶点v,对应keys[v]):数组从pq[0]代表原点其它顶点从pq[1]开始插入
22 */
23 private int[] pq;
24 /**
25 * 标记索引为i的元素在二叉堆中的位置。pq的反转数组(qp[index]=i):qp[pq[i]] = pq[qp[i]] = i
26 */
27 private int[] qp;
28
29 /**
30 * 元素有序数组(按照pq的索引赋值)
31 */
32 public Key[] keys;
33
34 /**
35 * 初始化一个空索引优先队列,索引范围:0 ~ maxN-1
36 *
37 * @param maxN the keys on this priority queue are index from {@code 0}
38 * {@code maxN - 1}
39 * @throws IllegalArgumentException if {@code maxN < 0}
40 */
41 public IndexMinPQ(int maxN) {
42 if (maxN < 0) throw new IllegalArgumentException();
43 this.maxN = maxN;
44 // 初始有0个元素
45 n = 0;
46 // 初始化键数组长度为maxN + 1
47 keys = (Key[]) new Comparable[maxN + 1];
48 // 初始化"键值对"数组长度为maxN + 1
49 pq = new int[maxN + 1];
50 // 初始化"值键对"数组长度为maxN + 1
51 qp = new int[maxN + 1];
52 // 遍历给"值键对"数组赋值-1,后续只要!=-1,即包含i
53 for (int i = 0; i <= maxN; i++)
54 qp[i] = -1;
55 }
56
57 /**
58 * Returns true if this priority queue is empty.
59 *
60 * @return {@code true} if this priority queue is empty;
61 * {@code false} otherwise
62 */
63 public boolean isEmpty() {
64 return n == 0;
65 }
66
67 /**
68 * Is {@code i} an index on this priority queue?
69 *
70 * @param i an index
71 * @return {@code true} if {@code i} is an index on this priority queue;
72 * {@code false} otherwise
73 * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
74 */
75 public boolean contains(int i) {
76 validateIndex(i);
77 return qp[i] != -1;
78 }
79
80 /**
81 * Returns the number of keys on this priority queue.
82 *
83 * @return the number of keys on this priority queue
84 */
85 public int size() {
86 return n;
87 }
88
89 /**
90 * 插入一个元素,将元素key关联索引i
91 *
92 * @param i an index
93 * @param key the key to associate with index {@code i}
94 * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
95 * @throws IllegalArgumentException if there already is an item associated
96 * with index {@code i}
97 */
98 public void insert(int i, Key key) {
99 validateIndex(i);
100 if (contains(i)) throw new IllegalArgumentException("index is already in the priority queue");
101 // 元素个数+1
102 n++;
103 // 索引为i的二叉堆位置为n
104 qp[i] = n;
105 // 二叉堆底部插入新元素,值=i
106 pq[n] = i;
107 // 索引i对应的元素赋值
108 keys[i] = key;
109 // 二叉堆中,上浮最后一个元素(小值上浮)
110 swim(n);
111 }
112
113 /**
114 * 返回最小元素的索引
115 *
116 * @return an index associated with a minimum key
117 * @throws NoSuchElementException if this priority queue is empty
118 */
119 public int minIndex() {
120 if (n == 0) throw new NoSuchElementException("Priority queue underflow");
121 return pq[1];
122 }
123
124 /**
125 * 返回最小元素(key)
126 *
127 * @return a minimum key
128 * @throws NoSuchElementException if this priority queue is empty
129 */
130 public Key minKey() {
131 if (n == 0) throw new NoSuchElementException("Priority queue underflow");
132 return keys[pq[1]];
133 }
134
135 /**
136 * 删除最小值key,并返回最小值(优先队列索引)
137 *
138 * @return an index associated with a minimum key
139 * @throws NoSuchElementException if this priority queue is empty
140 */
141 public int delMin() {
142 if (n == 0) throw new NoSuchElementException("Priority queue underflow");
143 // pq[1]即为索引最小值
144 int min = pq[1];
145 // 交换第一个元素和最后一个元素
146 exch(1, n--);
147 // 把新换来的第一个元素下沉
148 sink(1);
149 // 校验下沉后,最后一个元素是最小值
150 assert min == pq[n+1];
151 // 恢复初始值,-1即代表该元素已删除
152 qp[min] = -1; // delete
153 // 方便垃圾回收
154 keys[min] = null;
155 // 最后一个元素(索引)赋值-1
156 pq[n+1] = -1; // not needed
157 return min;
158 }
159
160 /**
161 * Returns the key associated with index {@code i}.
162 *
163 * @param i the index of the key to return
164 * @return the key associated with index {@code i}
165 * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
166 * @throws NoSuchElementException no key is associated with index {@code i}
167 */
168 public Key keyOf(int i) {
169 validateIndex(i);
170 if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
171 else return keys[i];
172 }
173
174 /**
175 * Change the key associated with index {@code i} to the specified value.
176 *
177 * @param i the index of the key to change
178 * @param key change the key associated with index {@code i} to this key
179 * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
180 * @throws NoSuchElementException no key is associated with index {@code i}
181 */
182 public void changeKey(int i, Key key) {
183 validateIndex(i);
184 if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
185 keys[i] = key;
186 swim(qp[i]);
187 sink(qp[i]);
188 }
189
190 /**
191 * Change the key associated with index {@code i} to the specified value.
192 *
193 * @param i the index of the key to change
194 * @param key change the key associated with index {@code i} to this key
195 * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
196 * @deprecated Replaced by {@code changeKey(int, Key)}.
197 */
198 @Deprecated
199 public void change(int i, Key key) {
200 changeKey(i, key);
201 }
202
203 /**
204 * 减小索引i对应的值为key
205 * 更新:
206 * 1.元素数组keys[]
207 * 2.小顶二叉堆pq[]
208 *
209 * @param i the index of the key to decrease
210 * @param key decrease the key associated with index {@code i} to this key
211 * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
212 * @throws IllegalArgumentException if {@code key >= keyOf(i)}
213 * @throws NoSuchElementException no key is associated with index {@code i}
214 */
215 public void decreaseKey(int i, Key key) {
216 validateIndex(i);
217 if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
218 // key 值一样,报错
219 if (keys[i].compareTo(key) == 0)
220 throw new IllegalArgumentException("Calling decreaseKey() with a key equal to the key in the priority queue");
221 // key比当前值大,报错
222 if (keys[i].compareTo(key) < 0)
223 throw new IllegalArgumentException("Calling decreaseKey() with a key strictly greater than the key in the priority queue");
224 // key比当前值小,把key赋值进去
225 keys[i] = key;
226 // 小值上浮(qp[i]=索引i在二叉堆pq[]中的位置)
227 swim(qp[i]);
228 }
229
230 /**
231 * Increase the key associated with index {@code i} to the specified value.
232 *
233 * @param i the index of the key to increase
234 * @param key increase the key associated with index {@code i} to this key
235 * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
236 * @throws IllegalArgumentException if {@code key <= keyOf(i)}
237 * @throws NoSuchElementException no key is associated with index {@code i}
238 */
239 public void increaseKey(int i, Key key) {
240 validateIndex(i);
241 if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
242 if (keys[i].compareTo(key) == 0)
243 throw new IllegalArgumentException("Calling increaseKey() with a key equal to the key in the priority queue");
244 if (keys[i].compareTo(key) > 0)
245 throw new IllegalArgumentException("Calling increaseKey() with a key strictly less than the key in the priority queue");
246 keys[i] = key;
247 sink(qp[i]);
248 }
249
250 /**
251 * Remove the key associated with index {@code i}.
252 *
253 * @param i the index of the key to remove
254 * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
255 * @throws NoSuchElementException no key is associated with index {@code i}
256 */
257 public void delete(int i) {
258 validateIndex(i);
259 if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
260 int index = qp[i];
261 exch(index, n--);
262 swim(index);
263 sink(index);
264 keys[i] = null;
265 qp[i] = -1;
266 }
267
268 // throw an IllegalArgumentException if i is an invalid index
269 private void validateIndex(int i) {
270 if (i < 0) throw new IllegalArgumentException("index is negative: " + i);
271 if (i >= maxN) throw new IllegalArgumentException("index >= capacity: " + i);
272 }
273
274 /***************************************************************************
275 * General helper functions.
276 ***************************************************************************/
277 private boolean greater(int i, int j) {
278 return keys[pq[i]].compareTo(keys[pq[j]]) > 0;
279 }
280
281 private void exch(int i, int j) {
282 int swap = pq[i];
283 pq[i] = pq[j];
284 pq[j] = swap;
285 qp[pq[i]] = i;
286 qp[pq[j]] = j;
287 }
288
289
290 /***************************************************************************
291 * Heap helper functions. 上浮
292 ***************************************************************************/
293 private void swim(int k) {
294 // 如果父节点值比当前节点值大,交换,父节点作为当前节点,轮询。即小值上浮。
295 while (k > 1 && greater(k/2, k)) {
296 exch(k, k/2);
297 k = k/2;
298 }
299 }
300 //下沉
301 private void sink(int k) {
302 while (2*k <= n) {
303 int j = 2*k;
304 if (j < n && greater(j, j+1)) j++;
305 if (!greater(k, j)) break;
306 exch(k, j);
307 k = j;
308 }
309 }
310
311
312 /***************************************************************************
313 * Iterators.
314 ***************************************************************************/
315
316 /**
317 * Returns an iterator that iterates over the keys on the
318 * priority queue in ascending order.
319 * The iterator doesn't implement {@code remove()} since it's optional.
320 *
321 * @return an iterator that iterates over the keys in ascending order
322 */
323 @Override
324 public Iterator<Integer> iterator() { return new HeapIterator(); }
325
326 private class HeapIterator implements Iterator<Integer> {
327 // create a new pq
328 private IndexMinPQ<Key> copy;
329
330 // add all elements to copy of heap
331 // takes linear time since already in heap order so no keys move
332 public HeapIterator() {
333 copy = new IndexMinPQ<Key>(pq.length - 1);
334 for (int i = 1; i <= n; i++)
335 copy.insert(pq[i], keys[pq[i]]);
336 }
337
338 @Override
339 public boolean hasNext() { return !copy.isEmpty(); }
340 @Override
341 public void remove() { throw new UnsupportedOperationException(); }
342
343 @Override
344 public Integer next() {
345 if (!hasNext()) throw new NoSuchElementException();
346 return copy.delMin();
347 }
348 }
349
350
351 /**
352 * Unit tests the {@code IndexMinPQ} data type.
353 *
354 * @param args the command-line arguments
355 */
356 public static void main(String[] args) {
357 // insert a bunch of strings
358 String[] strings = { "it", "was", "the", "best", "of", "times", "it", "was", "the", "worst" };
359
360 IndexMinPQ<String> pq = new IndexMinPQ<String>(strings.length);
361 for (int i = 0; i < strings.length; i++) {
362 pq.insert(i, strings[i]);
363 }
364
365 // delete and print each key
366 while (!pq.isEmpty()) {
367 int i = pq.delMin();
368 StdOut.println(i + " " + strings[i]);
369 }
370 StdOut.println();
371
372 // reinsert the same strings
373 for (int i = 0; i < strings.length; i++) {
374 pq.insert(i, strings[i]);
375 }
376
377 // print each key using the iterator
378 for (int i : pq) {
379 StdOut.println(i + " " + strings[i]);
380 }
381 while (!pq.isEmpty()) {
382 pq.delMin();
383 }
384
385 }
386 }
2.4 Prim算法(基于二叉堆)
1 package study.algorithm.graph;
2
3 import study.algorithm.base.*;
4
5 import java.util.Arrays;
6
7 /***
8 * @Description 使用Prim算法计算一棵最小生成树 27 *
28 * @author denny.zhang
29 * @date 2020/5/26 9:50 上午
30 */
31 public class PrimMST {
32 private static final double FLOATING_POINT_EPSILON = 1E-12;
33
34 /**
35 * 顶点索引,树顶点到非树顶点的最短边(距离树最近的边)
36 */
37 private Edge[] edgeTo;
38 /**
39 * 顶点索引,最短边的权重
40 */
41 private double[] distTo;
42 /**
43 * 顶点索引,标记顶点是否在最小生成树中
44 */
45 private boolean[] marked;
46 /**
47 * 有效的横切边(索引最小优先队列,索引为顶点v,值pq[v]=edgeTo[v].weight()=distTo[v])
48 */
49 private IndexMinPQ<Double> pq;
50
51 /**
52 * 计算一个边加权图的最小生成树
53 * @param G the edge-weighted graph
54 */
55 public PrimMST(EdgeWeightedGraph G) {
56 // 初始化3个顶点索引数组
57 edgeTo = new Edge[G.V()];
58 distTo = new double[G.V()];
59 marked = new boolean[G.V()];
60 // 初始化:顶点索引最小优先队列
61 pq = new IndexMinPQ<Double>(G.V());
62 for (int v = 0; v < G.V(); v++) {
63 // 初始化为无穷大
64 distTo[v] = Double.POSITIVE_INFINITY;
65 }
66 // 遍历顶点数
67 for (int v = 0; v < G.V(); v++)
68 {
69 // 如果顶点0开始能全进树,那就一次搞定
70 StdOut.println("v="+ v+",marked[v]="+marked[v]);
71 // 如果没进树
72 if (!marked[v]) {
73 StdOut.println("v="+v+",执行prim");
74 // 最小生成树
75 prim(G, v);
76 }
77 }
78
79 // 校验,可省略
80 assert check(G);
81 }
82
83
84 /**
85 * 从顶点s开始生成图G
86 * @param G
87 * @param s
88 */
89 private void prim(EdgeWeightedGraph G, int s) {
90 // 顶点s的权重=0
91 distTo[s] = 0.0;
92 // 顶点s进队列,key为索引,value为边权重
93 pq.insert(s, distTo[s]);
94 StdOut.println("顶点s进队列: s="+ s+",distTo[s]="+distTo[s]);
95 StdOut.println("pq="+ Arrays.toString(pq.keys));
96
97 // 循环
98 while (!pq.isEmpty()) {
99 // 取出最小权重边的顶点(最近的顶点)
100 int v = pq.delMin();
101 // 添加到树中
102 scan(G, v);
103 }
104 }
105
106 /**
107 * 将顶点V添加到树中,更新数据
108 * @param G
109 * @param v
110 */
111 private void scan(EdgeWeightedGraph G, int v) {
112 StdOut.println("v="+ v+",进树");
113 // 标记 进树
114 marked[v] = true;
115 // 遍历顶点v的邻接边
116 for (Edge e : G.adj(v)) {
117 StdOut.println("遍历顶点v的邻接边:v="+ v+",e="+e.toString());
118 // 另一个顶点
119 int w = e.other(v);
120 StdOut.println("w=" + w);
121 // 如果w已进树,跳过(至少有一个点不在树中,计算才有意义)
122 if (marked[w]) {
123 StdOut.println("已进树,跳过w=" + w);
124 continue;
125 }
126 // 如果边e的权重 < 当前到顶点w的权重
127 if (e.weight() < distTo[w]) {
128 StdOut.println("e.weight()="+e.weight()+" ,distTo[w]=" + distTo[w]);
129 // 更新最小权重
130 distTo[w] = e.weight();
131 // 连接w和树的最佳边变为e
132 edgeTo[w] = e;
133 // 顶点w在pq队列中
134 if (pq.contains(w)) {
135 StdOut.println("顶点w在pq队列中:w="+w);
136 // 减小w索引对应的权重值,小值上浮
137 pq.decreaseKey(w, distTo[w]);
138 // 顶点w不在队列中
139 } else {
140 StdOut.println("顶点w不在pq队列中,插入队列前:w="+w+",pq="+ Arrays.toString(pq.keys));
141 // 插入队列
142 pq.insert(w, distTo[w]);
143 StdOut.println("顶点w不在pq队列中,插入队列后:w="+w+",pq="+Arrays.toString(pq.keys));
144 }
145 }
146 }
147 }
148
149 /**
150 * Returns the edges in a minimum spanning tree (or forest).
151 * @return the edges in a minimum spanning tree (or forest) as
152 * an iterable of edges
153 */
154 public Iterable<Edge> edges() {
155 Queue<Edge> mst = new Queue<Edge>();
156 for (int v = 0; v < edgeTo.length; v++) {
157 Edge e = edgeTo[v];
158 if (e != null) {
159 mst.enqueue(e);
160 }
161 }
162 return mst;
163 }
164
165 /**
166 * Returns the sum of the edge weights in a minimum spanning tree (or forest).
167 * @return the sum of the edge weights in a minimum spanning tree (or forest)
168 */
169 public double weight() {
170 double weight = 0.0;
171 for (Edge e : edges()) {
172 weight += e.weight();
173 }
174 return weight;
175 }
176
177
178 // check optimality conditions (takes time proportional to E V lg* V)
179 private boolean check(EdgeWeightedGraph G) {
180
181 // check weight
182 double totalWeight = 0.0;
183 for (Edge e : edges()) {
184 totalWeight += e.weight();
185 }
186 if (Math.abs(totalWeight - weight()) > FLOATING_POINT_EPSILON) {
187 System.err.printf("Weight of edges does not equal weight(): %f vs. %f\n", totalWeight, weight());
188 return false;
189 }
190
191 // check that it is acyclic
192 UF uf = new UF(G.V());
193 for (Edge e : edges()) {
194 int v = e.either(), w = e.other(v);
195 if (uf.find(v) == uf.find(w)) {
196 System.err.println("Not a forest");
197 return false;
198 }
199 uf.union(v, w);
200 }
201
202 // check that it is a spanning forest
203 for (Edge e : G.edges()) {
204 int v = e.either(), w = e.other(v);
205 if (uf.find(v) != uf.find(w)) {
206 System.err.println("Not a spanning forest");
207 return false;
208 }
209 }
210
211 // check that it is a minimal spanning forest (cut optimality conditions)
212 for (Edge e : edges()) {
213
214 // all edges in MST except e
215 uf = new UF(G.V());
216 for (Edge f : edges()) {
217 int x = f.either(), y = f.other(x);
218 if (f != e) {
219 uf.union(x, y);
220 }
221 }
222
223 // check that e is min weight edge in crossing cut
224 for (Edge f : G.edges()) {
225 int x = f.either(), y = f.other(x);
226 if (uf.find(x) != uf.find(y)) {
227 if (f.weight() < e.weight()) {
228 System.err.println("Edge " + f + " violates cut optimality conditions");
229 return false;
230 }
231 }
232 }
233
234 }
235
236 return true;
237 }
238
239 public static void main(String[] args) {
240 // 读取图文件
241 In in = new In(args[0]);
242 // 初始化:边加权无向图
243 EdgeWeightedGraph G = new EdgeWeightedGraph(in);
244 // 核心算法Prim
245 PrimMST mst = new PrimMST(G);
246 // 打印全部边
247 for (Edge e : mst.edges()) {
248 StdOut.println(e);
249 }
250 // 打印总权重
251 StdOut.printf("%.5f\n", mst.weight());
252 }
253
254
255 }
运行方法:
使用导入文件的方式,后续只需要修改文件内容,即可执行,比较方便。8个顶点,16条边的边加权无向图,内容如下:
8 16
4 5 0.35
4 7 0.37
5 7 0.28
0 7 0.16
1 5 0.32
0 4 0.38
2 3 0.17
1 7 0.19
0 2 0.26
1 2 0.36
1 3 0.29
2 7 0.34
6 2 0.40
3 6 0.52
6 0 0.58
6 4 0.93
运行配置:
运行:
结合邻接链表来看结果,其实就是遍历了一遍邻接表。
v=0,marked[v]=false----从顶点0作为原点,构建最小生成树,---begin!
v=0,执行prim
顶点s进队列: s=0,distTo[s]=0.0
pq=[0.0, null, null, null, null, null, null, null, null]
v=0,进树---》顶点0开始 另一个顶点: 6 2 4 7
遍历顶点v的邻接边:v=0,e=6-0 0.58000
w=6
e.weight()=0.58 ,distTo[w]=Infinity
顶点w不在pq队列中,插入队列前:w=6,pq=[null, null, null, null, null, null, null, null, null]
顶点w不在pq队列中,插入队列后:w=6,pq=[null, null, null, null, null, null, 0.58, null, null]
遍历顶点v的邻接边:v=0,e=0-2 0.26000
w=2
e.weight()=0.26 ,distTo[w]=Infinity
顶点w不在pq队列中,插入队列前:w=2,pq=[null, null, null, null, null, null, 0.58, null, null]
顶点w不在pq队列中,插入队列后:w=2,pq=[null, null, 0.26, null, null, null, 0.58, null, null]
遍历顶点v的邻接边:v=0,e=0-4 0.38000
w=4
e.weight()=0.38 ,distTo[w]=Infinity
顶点w不在pq队列中,插入队列前:w=4,pq=[null, null, 0.26, null, null, null, 0.58, null, null]
顶点w不在pq队列中,插入队列后:w=4,pq=[null, null, 0.26, null, 0.38, null, 0.58, null, null]
遍历顶点v的邻接边:v=0,e=0-7 0.16000
w=7
e.weight()=0.16 ,distTo[w]=Infinity
顶点w不在pq队列中,插入队列前:w=7,pq=[null, null, 0.26, null, 0.38, null, 0.58, null, null]
顶点w不在pq队列中,插入队列后:w=7,pq=[null, null, 0.26, null, 0.38, null, 0.58, 0.16, null]
v=7,进树---》顶点7开始 另一个顶点: 2 1 0 5 4
遍历顶点v的邻接边:v=7,e=2-7 0.34000
w=2
遍历顶点v的邻接边:v=7,e=1-7 0.19000
w=1
e.weight()=0.19 ,distTo[w]=Infinity
顶点w不在pq队列中,插入队列前:w=1,pq=[null, null, 0.26, null, 0.38, null, 0.58, null, null]
顶点w不在pq队列中,插入队列后:w=1,pq=[null, 0.19, 0.26, null, 0.38, null, 0.58, null, null]
遍历顶点v的邻接边:v=7,e=0-7 0.16000
w=0
已进树,跳过w=0
遍历顶点v的邻接边:v=7,e=5-7 0.28000
w=5
e.weight()=0.28 ,distTo[w]=Infinity
顶点w不在pq队列中,插入队列前:w=5,pq=[null, 0.19, 0.26, null, 0.38, null, 0.58, null, null]
顶点w不在pq队列中,插入队列后:w=5,pq=[null, 0.19, 0.26, null, 0.38, 0.28, 0.58, null, null]
遍历顶点v的邻接边:v=7,e=4-7 0.37000
w=4
e.weight()=0.37 ,distTo[w]=0.38
顶点w在pq队列中:w=4
v=1,进树---》顶点1开始 另一个顶点: 3 2 7 5
遍历顶点v的邻接边:v=1,e=1-3 0.29000
w=3
e.weight()=0.29 ,distTo[w]=Infinity
顶点w不在pq队列中,插入队列前:w=3,pq=[null, null, 0.26, null, 0.37, 0.28, 0.58, null, null]
顶点w不在pq队列中,插入队列后:w=3,pq=[null, null, 0.26, 0.29, 0.37, 0.28, 0.58, null, null]
遍历顶点v的邻接边:v=1,e=1-2 0.36000
w=2
遍历顶点v的邻接边:v=1,e=1-7 0.19000
w=7
已进树,跳过w=7
遍历顶点v的邻接边:v=1,e=1-5 0.32000
w=5
v=2,进树---》顶点2开始 另一个顶点: 6 7 1 0 3
遍历顶点v的邻接边:v=2,e=6-2 0.40000
w=6
e.weight()=0.4 ,distTo[w]=0.58
顶点w在pq队列中:w=6
遍历顶点v的邻接边:v=2,e=2-7 0.34000
w=7
已进树,跳过w=7
遍历顶点v的邻接边:v=2,e=1-2 0.36000
w=1
已进树,跳过w=1
遍历顶点v的邻接边:v=2,e=0-2 0.26000
w=0
已进树,跳过w=0
遍历顶点v的邻接边:v=2,e=2-3 0.17000
w=3
e.weight()=0.17 ,distTo[w]=0.29
顶点w在pq队列中:w=3
v=3,进树---》顶点3开始 另一个顶点: 6 1 2 5
遍历顶点v的邻接边:v=3,e=3-6 0.52000
w=6
遍历顶点v的邻接边:v=3,e=1-3 0.29000
w=1
已进树,跳过w=1
遍历顶点v的邻接边:v=3,e=2-3 0.17000
w=2
已进树,跳过w=2
v=5,进树---》顶点5开始 另一个顶点: 1 7 4
w=1
已进树,跳过w=1
遍历顶点v的邻接边:v=5,e=5-7 0.28000
w=7
已进树,跳过w=7
遍历顶点v的邻接边:v=5,e=4-5 0.35000
w=4
e.weight()=0.35 ,distTo[w]=0.37
顶点w在pq队列中:w=4
v=4,进树---》顶点4开始 另一个顶点:6 0 7 5
遍历顶点v的邻接边:v=4,e=6-4 0.93000
w=6
遍历顶点v的邻接边:v=4,e=0-4 0.38000
w=0
已进树,跳过w=0
遍历顶点v的邻接边:v=4,e=4-7 0.37000
w=7
已进树,跳过w=7
遍历顶点v的邻接边:v=4,e=4-5 0.35000
w=5
已进树,跳过w=5
v=6,进树---》顶点6开始 另一个顶点:4 0 3 2
遍历顶点v的邻接边:v=6,e=6-4 0.93000
w=4
已进树,跳过w=4
遍历顶点v的邻接边:v=6,e=6-0 0.58000
w=0
已进树,跳过w=0
遍历顶点v的邻接边:v=6,e=3-6 0.52000
w=3
已进树,跳过w=3
遍历顶点v的邻接边:v=6,e=6-2 0.40000
w=2
已进树,跳过w=2----从顶点0作为原点,构建最小生成树,---end!
v=1,marked[v]=true----从顶点1作为原点,构建最小生成树,已经入树,跳过!后续节点都已进树,都跳过。
v=2,marked[v]=true
v=3,marked[v]=true
v=4,marked[v]=true
v=5,marked[v]=true
v=6,marked[v]=true
v=7,marked[v]=true
1-7 0.19000---》打印最小生成树的边 权重
0-2 0.26000
2-3 0.17000
4-5 0.35000
5-7 0.28000
6-2 0.40000
0-7 0.16000
1.810000---》打印最小生成树的总权重
三、总结
使用二叉堆(索引小值优先队列实现)优化的Prim算法,
3.1 空间复杂度
构造了几个顶点v索引的数组,所以和顶点数v成正比。
3.2 时间复杂度
主要就在优先队列的操作上
- 1.PrimMST->prim中:共v个顶点,每次取出最小值pq.delMin() 取第一个最小值元素,并把最后一个元素填充,新上来的这个元素下沉sink()大值下沉,时间复杂度=logv, 共v个顶点,也就是vlogv。
- 2.PrimMST->prim->scan 中 :遍历顶点的邻接表(邻接边链表),不管是 更新更小值 decreaseKey()、还是 插入新值 insert() ->都要执行swim()小值上浮, 时间复杂度=logv,一共E条边,所以Elogv.
所以:O(vlgv+elgv)=ElgV
3.3. 优化
1.如果使用斐波那契堆也就是Fredman-Tarjan算法,稠密图的decreaseKey操作耗时可以降到O(1),所以总耗时=O(V*logV+E*O(1))=ElgV
2.接近线性的算法Chazelle,但算法很复杂,就不再描述。
下一节,我们分析Kruskal算法。
最小生成树(二)Prim算法的更多相关文章
- Hihocoder 之 #1097 : 最小生成树一·Prim算法 (用vector二维 模拟邻接表,进行prim()生成树算法, *【模板】)
#1097 : 最小生成树一·Prim算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 最近,小Hi很喜欢玩的一款游戏模拟城市开放出了新Mod,在这个Mod中,玩家可 ...
- 最小生成树的Prim算法
构造最小生成树的Prim算法 假设G=(V,E)为一连通网,其中V为网中所有顶点的集合,E为网中所有带权边的集合.设置两个新的集合U和T,其中集合U用于存放G的最小生成树的顶点,集合T用于 ...
- hihocoder#1098 : 最小生成树二·Kruscal算法
#1098 : 最小生成树二·Kruscal算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 随着小Hi拥有城市数目的增加,在之间所使用的Prim算法已经无法继续使用 ...
- Hihocoder #1098 : 最小生成树二·Kruskal算法 ( *【模板】 )
#1098 : 最小生成树二·Kruscal算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 随着小Hi拥有城市数目的增加,在之间所使用的Prim算法已经无法继续使用 ...
- C++编程练习(10)----“图的最小生成树“(Prim算法、Kruskal算法)
1.Prim 算法 以某顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树. 2.Kruskal 算法 直接寻找最小权值的边来构建最小生成树. 比较: Kruskal 算法主要是针对边来展开,边数 ...
- 最小生成树一·Prim算法
描述 最近,小Hi很喜欢玩的一款游戏模拟城市开放出了新Mod,在这个Mod中,玩家可以拥有不止一个城市了! 但是,问题也接踵而来——小Hi现在手上拥有N座城市,且已知这N座城市中任意两座城市之间建造道 ...
- 数据结构与算法--最小生成树之Prim算法
数据结构与算法--最小生成树之Prim算法 加权图是一种为每条边关联一个权值或称为成本的图模型.所谓生成树,是某图的一棵含有全部n个顶点的无环连通子图,它有n - 1条边.最小生成树(MST)是加权图 ...
- 24最小生成树之Prim算法
最小生成树的Prim算法 思想:采用子树延伸法 将顶点分成两类: 生长点——已经在生成树上的顶点 非生长点——未长到生成树上的顶点 使用待选边表: 每个非生长点在待选边表中有一条待选边,一端连着非生长 ...
- hihocoder 1097 最小生成树一·Prim算法
#1097 : 最小生成树一·Prim算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 最近,小Hi很喜欢玩的一款游戏模拟城市开放出了新Mod,在这个Mod中,玩家可 ...
- 算法学习记录-图——最小生成树之prim算法
一个连通图的生成树是一个极小的连通子图,它包含图中全部的顶点(n个顶点),但只有n-1条边. 最小生成树:构造连通网的最小代价(最小权值)生成树. prim算法在严蔚敏树上有解释,但是都是数学语言,很 ...
随机推荐
- centos7下安装postgresql-10.3
centos7下安装pgsql10.3 前言 下载pgsql-10.3 安装 解压 安装基本的工具 编译 安装 创建目录 data.log 加入系统环境变量 增加用户 postgres 并赋权 初始化 ...
- 【七】强化学习之Policy Gradient---PaddlePaddlle【PARL】框架{飞桨}
相关文章: [一]飞桨paddle[GPU.CPU]安装以及环境配置+python入门教学 [二]-Parl基础命令 [三]-Notebook.&pdb.ipdb 调试 [四]-强化学习入门简 ...
- 1.1 Windows驱动开发:配置驱动开发环境
在进行驱动开发之前,您需要先安装适当的开发环境和工具.首先,您需要安装Windows驱动开发工具包(WDK),这是一组驱动开发所需的工具.库.示例和文档.然后,您需要安装Visual Studio开发 ...
- 小团队如何妙用 JuiceFS
早些年还在 ENJOY 的时候, 就已经在用 JuiceFS, 并且一路伴随着我工作过的四家小公司, 这玩意对我来说, 已经成了理所应当不可或缺的基础设施, 对于我服务过的小团队而言, 更是实实在在的 ...
- 使用ethtool排查网卡速率问题
今天去现场帮一个客户排查备份网络速率问题. 用户期望是万兆的速率,但实际上目前只有千兆,因为目前上面运行着数据库,且数据量较大,千兆的备份网络速率不能满足用户备份数据库的时长要求. 首先,确认备份网络 ...
- SESSION会话机制解析
Windows Session(会话)的概念 会话 session 是由代表单个用户登录会话的所有进程和系统对象组成的.其中的对象包括所有的窗口,桌面和windows stations.桌面是特定se ...
- Pandas—read_csv()/read_table()文本文件的读取
对于CSV及txt后缀的文本文件,分别使用pandas模块中的read_csv函数和read_table函数 文件类型 函数名称 CSV read_csv() txt read_table() 1. ...
- Python-pymysql查询MySQL的表
一.安装pymysql py -m pip install pymysql; 二.创建表并插入数据 CREATE TABLE `course` ( `course_id` varchar(10) DE ...
- HWS山大专区PWN双一血 & CRYPTO-WP
2023.11.18 两天半的比赛,就打了半天(因为要赶去打香山杯决赛了),不过结果还算好,人生第一次拿了两个一血hhh.写wp的时候人在中大南校北门的酒店里:) controller 格式化字符串泄 ...
- SP21690 POWERUP - Power the Power Up 题解
题目传送门 前置知识 扩展欧拉定理 解法 直接对 \(a\) 和 \(b^c\) 分讨,跑一遍扩展欧拉定理就行了. 另外由于本题的特殊规定 \(0^0=1\),故需要在当 \(a=0\) 时,对 \( ...