开发一种不断使用环的性质的算法来计算最小生成树
环的性质
当构建出一副无向图的最小生成树时可以发现,在无向图中任意将一条边加入最小生成树,都会构建出一个环;因为生成树(不一定最小)实际上会达到图中的所有点,再加入任何一条边都相当于多连接了一遍两个顶点,必然会产生环。
利用这个性质,可一每次都从新产生的环中删除权重最大的边,来不停构造最小生成树。
Vyssotsky
首先可以先随便拟定一个生成树(不一定最小):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| edges = new Queue<Edge>(); mst = new EdgeWeightedGraph(G.V()); boolean[] marked = new boolean[G.V()]; for (Edge e: G.edges()) { int v = e.either(); int w = e.other(v); if (marked[v] && marked[w]) { edges.enqueue(e); continue; } marked[v] = true; marked[w] = true; mst.addEdge(e); }
|
使用marked
来记录点是否已经被连接过,如果两个顶点均被连接过,则将该条边放入备选edges
中,否则加入新图,也就是生成树中。
然后不停地将备选边中的边加入到生成树中:
1 2 3 4
| while (edges.size() > 0) { Edge e = edges.dequeue(); mst.addEdge(e); }
|
此时一定会产生环,使用深度优先算法对环进行检测,从而找到权重最大的边:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public boolean dfs(int start, int end, int skip) { for (Edge e: mst.adj(start)) { if (set.contains(e.weight())) continue;
int v = e.other(start); if (v == skip) continue;
if (v == end || dfs(v, end, start)) { max = Math.max(max, e.weight()); return true; } } return false; }
|
其中寻找的时候要把前一条边的起点忽略掉,这里就没没有再使用marked
数组了。
删除边
然后就是要将权重最大的边删除掉,这里给的加权无向图EdgeWeightedGraph
中没有提供删除边的API,也没有说需要自行扩展,这里就利用了所有权重均不相同的性质,将最大权重加入到集合中:
1 2 3 4 5 6 7 8 9
| while (edges.size() > 0) { Edge e = edges.dequeue(); max = e.weight(); mst.addEdge(e); int v = e.either(); dfs(v, e.other(v), e.other(v)); set.add(max); }
|
这样虽然是全部边都加入了新图中,但是在我们获取最小生成树的时候,如果遇到了边的权重在集合中,则进行跳过:
1 2 3 4 5 6 7 8
| public Iterable<Edge> edges() { Queue<Edge> queue = new Queue<Edge>(); for (Edge e: mst.edges()) { if (set.contains(e.weight())) continue; queue.enqueue(e); } return queue; }
|
写了下测试,可以通过。整体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| import java.util.HashSet;
import edu.princeton.cs.algs4.*;
class VyssotskyMST { private Queue<Edge> edges; private EdgeWeightedGraph mst; private HashSet set; private double max; public VyssotskyMST(EdgeWeightedGraph G) { edges = new Queue<Edge>(); set = new HashSet<Double>(); mst = new EdgeWeightedGraph(G.V()); boolean[] marked = new boolean[G.V()]; for (Edge e: G.edges()) { int v = e.either(); int w = e.other(v); if (marked[v] && marked[w]) { edges.enqueue(e); continue; } marked[v] = true; marked[w] = true; mst.addEdge(e); }
while (edges.size() > 0) { Edge e = edges.dequeue(); max = e.weight(); mst.addEdge(e); int v = e.either(); dfs(v, e.other(v), e.other(v)); set.add(max); } }
public boolean dfs(int start, int end, int skip) { for (Edge e: mst.adj(start)) { if (set.contains(e.weight())) continue;
int v = e.other(start); if (v == skip) continue;
if (v == end || dfs(v, end, start)) { max = Math.max(max, e.weight()); return true; } } return false; }
public Iterable<Edge> edges() { Queue<Edge> queue = new Queue<Edge>(); for (Edge e: mst.edges()) { if (set.contains(e.weight())) continue; queue.enqueue(e); } return queue; }
public static void main(String[] args) { In in = new In(args[0]); EdgeWeightedGraph G = new EdgeWeightedGraph(in); VyssotskyMST mst = new VyssotskyMST(G); for (Edge e: mst.edges()) { System.out.println(e); } } }
|
自己描述一边算法也是对思路的一个整理,现在发现其实在第一次遍历中就可以开始环的判断了,没有必要收集起来再做遍历。
目前自己都觉得性能不太好,之后有机会还是会进行优化。