最小生成树是怎么用最短的路径把图所有的结点给连起来。
这里有两种方式,一种是Kruskal方法,简称K方法,另一种是Prim方法,简称P方法。
K方法:是根据最小权重的边来算的,通过一个优先级队列来存放所有的边,权值最小的在队尾。优先级队列其实就是最大堆和最小堆,看你传进去的比较器是按什么方式排列的。通过一个并查集来存放所有的结点,并查集是看两个元素是否是一个集合和把两个集合合并成一个集合的功能。关于并查集可以看我另一篇文章。再通过一个set来确认边是否已经加入。每次取出最小的边,看边的两头结点是否是一个集合,若不是就把边加入set,在把这两个结点通过并查集合并成一个集合。若是一个集合就从队列里再取出一条边来计算。代码如下所示:
public class KruskalMST {
//并查集
public static class UnionFind {
private HashMap<Node, Node> fatherMap;
private HashMap<Node, Integer> rankMap;
public UnionFind() {
fatherMap = new HashMap<Node, Node>();
rankMap = new HashMap<Node, Integer>();
}
private Node findFather(Node n) {
Node father = fatherMap.get(n);
if (father != n) {
father = findFather(father);
}
fatherMap.put(n, father);
return father;
}
public void makeSets(Collection<Node> nodes) {
fatherMap.clear();
rankMap.clear();
for (Node node : nodes) {
fatherMap.put(node, node);
rankMap.put(node, 1);
}
}
public boolean isSameSet(Node a, Node b) {
return findFather(a) == findFather(b);
}
public void union(Node a, Node b) {
if (a == null || b == null) {
return;
}
Node aFather = findFather(a);
Node bFather = findFather(b);
if (aFather != bFather) {
int aFrank = rankMap.get(aFather);
int bFrank = rankMap.get(bFather);
if (aFrank <= bFrank) {
fatherMap.put(aFather, bFather);
rankMap.put(bFather, aFrank + bFrank);
} else {
fatherMap.put(bFather, aFather);
rankMap.put(aFather, aFrank + bFrank);
}
}
}
}
//比较器
public static class EdgeComparator implements Comparator<Edge> {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
public static Set<Edge> kruskalMST(Graph graph) {
UnionFind unionFind = new UnionFind();
unionFind.makeSets(graph.nodes.values());
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
for(Edge edge : graph.edges) {
priorityQueue.add(edge);
}
Set<Edge> result = new HashSet<>();
while(!priorityQueue.isEmpty()) {
Edge edge = priorityQueue.poll();
if(!unionFind.isSameSet(edge.from,edge.to)) {
result.add(edge);
unionFind.union(edge.from,edge.to);
}
}
return result;
}
}
P算法:主要是根据结点来算的,首先得到一个结点和他所有的边,把结点放进set里,边放进优先级队列里,取出最小权值的边,看边的指向的结点是否在set里,不在就把结点放进set里,同时把这条边放进另一个set里,把这个结点的边放进优先级队列里,代码如下所示:
public class Prim {
public static class EdgeComparator implements Comparator<Edge> {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
public static Set<Edge> primMST(Graph graph) {
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
HashSet<Node> set = new HashSet<>();
Set<Edge> result = new HashSet<>();
for(Node node : graph.nodes.values()) {
if(!set.contains(node)) {
set.add(node);
for(Edge edge : node.edges) {
priorityQueue.add(edge);
}
while(!priorityQueue.isEmpty()) {
Edge edge = priorityQueue.poll();
Node toNode = edge.to;
if(!set.contains(toNode)) {
set.add(toNode);
result.add(edge);
for(Edge nextEdge : toNode.edges) {
priorityQueue.add(nextEdge);
}
}
}
}
}
return result;
}
}
关于图的结构可看我另一篇关于图的生成和深度宽度优先遍历的文章。