最小生成树,指的是对于一个无向图,使得点连通的最小权值和的边集,就是最小生成树。
最小生成树的计算有两种经典的算法:Kruskal算法、Prim算法。
Kruskal算法:从边的角度考虑,边权重从小到大排序,依次考虑每条边加入是否形成环,形成环,则不要该条边;否则要该条边。
难点:如何判断加入一条边的时候是否形成环?需要利用并查集。如果当前边的两个节点在一个集合里,说明之前已经有边使得这两个点连通了,所以就不能再加入当前边。
Prim算法:从点的角度出发,任选一个点出发,当选择了该点时,其对应的边也都解锁了,从解锁的边里选择权重最小的边,判断该边的另一个点是否已经加过,没有加过,就加入该点,并将该点对应的边都解锁。然后从所有解锁的边里,找最小的边,循环上面的过程,直到所有点都被加入。
public Set<GraphEdge> KruskalMST(Graph graph){
Set<GraphEdge> res=new HashSet();
//初始化并查集,这里是一个简单的实现,后续写并查集的优雅实现查找和合并都是O(1)
MyUnionSet unionSet = new MyUnionSet((new ArrayList<>(graph.nodes.values()));
List<GraphEdge> edges=graph.edges;
edges.sort(o1,o2->(o1-o2));
for(GraphEdge edge:edges){
if(!isSameSet(edge.from,edge.to)){
res.add(edge);
union(edge.from,edge.to);
}
}
return res;
}
public Set<GraphEdge> PrimMST(Graph graph) {
Set<GraphEdge> res = new HashSet<>();
Set<GraphNode> set = new HashSet<>();
PriorityQueue<GraphEdge> heap = new PriorityQueue<>((o1, o2) -> o1.weight - o2.weight);
//for循环是为了解决森林的问题
for (GraphNode node : graph.nodes.values()) {
if (!set.contains(node)) {
set.add(node);
heap.addAll(node.edges);
}
while (!heap.isEmpty()) {
GraphEdge edge = heap.poll();
if (!set.contains(edge.to)) {
res.add(edge);
set.add(edge.to);
heap.addAll(edge.to.edges);
}
}
}
return res;
}
//简单的并查集实现
public class MyUnionSet {
public Map<GraphNode, List<GraphNode>> setMap;
public MyUnionSet(List<GraphNode> nodes) {
setMap = new HashMap<>();
for (GraphNode node : nodes) {
setMap.put(node, new ArrayList<>(nodes));
}
}
public boolean isSameSet(GraphNode node1, GraphNode node2) {
List<GraphNode> list1 = setMap.get(node1);
List<GraphNode> list2 = setMap.get(node2);
return list1 == list2;
}
public void union(GraphNode node1, GraphNode node2) {
if (!isSameSet(node1, node2)) {
List<GraphNode> list1 = setMap.get(node1);
List<GraphNode> list2 = setMap.get(node2);
for (GraphNode node : list2) {
list1.add(node);
setMap.put(node, list1);
}
}
}
}