最小生成树算法Kruskal(克鲁斯卡尔)
首先我们把所有的边按照权值先从小到大排列,接着按照顺序选取每条边,如果这条边的两个端点不属于同一集合,那么就将它们合并,直到所有的点都属于同一个集合,就生成了最小生成树。Kruskal算法依照的是贪心算法思想,总是在局部的当前步做出最优决定,最后得到全局最优决定。判断和合并过程中会使用到并查集结构。
步骤
- 把所有的边根据权值从小到大排序
- 依次从小到大判断边两端的点是否是一个集合(并查集),如果是一个集合就跳过如果不是一个集合,就把两个点放到一个集合中。
- 最后所有点连在一个集合中,算法完成。
public class Kruskal {
public static class UnionFind {
// key 某一个节点, value key节点往上的节点
public HashMap<Node, Node> fatherMap;
// key 某一个集合的代表节点, value key所在集合的节点个数
public HashMap<Node, Integer> sizeMap;
public UnionFind() {
fatherMap = new HashMap<Node,Node>();
sizeMap = new HashMap<Node,Integer>();
}
public void makeSets(Collection<Node> nodes) {
fatherMap.clear();
sizeMap.clear();
for (Node node : nodes) {
fatherMap.put(node, node);
sizeMap.put(node, 1);
}
}
public Node findFather(Node node) {
Stack<Node> path = new Stack<>();
while (node != fatherMap.get(node)) {
path.add(node);
node = fatherMap.get(node);
}
while (!path.isEmpty()) {
fatherMap.put(path.pop(), node);
}
return node;
}
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 aHead = findFather(a);
Node bHead = findFather(b);
if (aHead != bHead) {
int aSize = sizeMap.get(aHead);
int bSize = sizeMap.get(bHead);
Node big = aSize >= bSize ? aHead : bHead;
Node small = big == aHead ? bHead : aHead;
fatherMap.put(small, big);
sizeMap.put(big, aSize + bSize);
fatherMap.remove(small);
}
}
}
//定义按照边权重从小到大的比较器
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) {// M 条边
priorityQueue.add(edge);// O(logM)
}
Set<Edge> result = new HashSet<>();
while (!priorityQueue.isEmpty()) {// M 条边
Edge edge = priorityQueue.poll();// O(logM)
if (!unionFind.isSameSet(edge.from, edge.to)) { // O(1)
result.add(edge);
unionFind.union(edge.from, edge.to);
}
}
return result;
}
}
最小生成树算法Prim(普里姆)
Prim基于贪心算法思想,由图中一个点出发,从这个点出发的所有边中,挑选出权值最小的边(总是在当前步做出最优决定),从而把这两个点连通,按照这个策略,最后生成最小生成树(最后得出全局最优决定)。
步骤
- 可以从任意节点出发来寻找最小生成树
- 某个点加入到被选取的点中后,解锁这个点出发的所有新的边
- 在所有解锁的边中选最小的边,然后看看这个边会不会形成环
- 如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3
- 如果不会,要当前边,将改边的指向点加入到被选取的点中,重复2
- 当所有点都被选取,最小生成树就得到了
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> nodeSet = new HashSet<>();
//哪些边被选择了
HashSet<Edge> edgeSet = new HashSet<>();
// 依次挑选的的边在result里
Set<Edge> result = new HashSet<>();
for (Node node : graph.nodes.values()) {// 随便挑了一个点,防森林
// node 是开始点
if (!nodeSet.contains(node)) {
nodeSet.add(node);
}
for (Edge edge : node.edges) {// 由一个点,解锁所有相连的边
if (!edgeSet.contains(edge)) {
priorityQueue.add(edge);
edgeSet.add(edge);
}
}
while (!priorityQueue.isEmpty()) {
Edge edge = priorityQueue.poll();// 弹出解锁的边中,最小的边
Node toNode = edge.to;// 可能的一个新的点
if (!nodeSet.contains(toNode)) {// 不含有的时候,就是新的点
nodeSet.add(toNode);
result.add(edge);
for (Edge nextEdge : toNode.edges) {
if (!edgeSet.contains(nextEdge)) {
priorityQueue.add(nextEdge);
edgeSet.add(nextEdge);
}
}
}
}
}
return result;
}
}