图的应用最小生成树算法

「这是我参与2022首次更文挑战的第39天,活动详情查看:2022首次更文挑战」。

一、最小生成树算法之Kruskal

1、分析

所谓最小生成树是在不影响所有节点连通的情况下,所有边加起来最小值是多少

image.png

考察所有的边,从小到大依次考察(贪心),如果当前的边不会形成环,就要这条边,如果当前的边会形成环,就不要这条边

并查集走起:依次连接,依次合并

并查集合并过程略过,不会的看并查集章节

2、实现

// Union-Find Set
public static class UnionFind {
    // key 某一个节点, value key节点往上的节点
    private HashMap<Node, Node> fatherMap;
    // key 某一个集合的代表节点, value key所在集合的节点个数
    private HashMap<Node, Integer> sizeMap;

    public UnionFind() {
        fatherMap = new HashMap<>();
        sizeMap = new HashMap<>();
    }

    public void makeSets(Collection<Node> nodes) {
        fatherMap.clear();
        sizeMap.clear();
        for (Node node : nodes) {
            fatherMap.put(node, node);
            sizeMap.put(node, 1);
        }
    }

    private Node findFather(Node n) {
        Stack<Node> path = new Stack<>();
        while (n != fatherMap.get(n)) {
            path.add(n);
            n = fatherMap.get(n);
        }
        while (!path.isEmpty()) {
            fatherMap.put(path.pop(), n);
        }
        return n;
    }

    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 aDai = findFather(a);
        Node bDai = findFather(b);
        if (aDai != bDai) {
            int aSetSize = sizeMap.get(aDai);
            int bSetSize = sizeMap.get(bDai);
            if (aSetSize <= bSetSize) {
                fatherMap.put(aDai, bDai);
                sizeMap.put(bDai, aSetSize + bSetSize);
                sizeMap.remove(aDai);
            } else {
                fatherMap.put(bDai, aDai);
                sizeMap.put(aDai, aSetSize + bSetSize);
                sizeMap.remove(bDai);
            }
        }
    }
}


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

1、分析

Prim思路:点解锁边,再选一个点,再去解锁边

点->边->点->边...

image.png

2、实现

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<>();
    Set<Edge> result = new HashSet<>(); // 依次挑选的的边在result里
    for (Node node : graph.nodes.values()) { // 随便挑了一个点
        // node 是开始点
        if (!nodeSet.contains(node)) {
            nodeSet.add(node);
            for (Edge edge : node.edges) { // 由一个点,解锁所有相连的边
                priorityQueue.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) {
                        priorityQueue.add(nextEdge);
                    }
                }
            }
        }
        // break; 防止森林
    }
    return result;
}
复制代码

三、总结

最小生成树都是无向图

最小生成树算法之Kruskal:

  1. 总是从权值最小的边开始考虑,依次考察权值依次变大的边
  2. 当前的边要么进入最小生成树的集合,要么丢弃
  3. 如果当前的边进入最小生成树的集合中不会形成环,就要当前边
  4. 如果当前的边进入最小生成树的集合中会形成环,就不要当前边
  5. 考察完所有边之后,最小生成树的集合也得到了

最小生成树算法之Prim:

  1. 可以从任意节点出发来寻找最小生成树
  2. 某个点加入到被选取的点中后,解锁这个点出发的所有新的边
  3. 在所有解锁的边中选最小的边,然后看看这个边会不会形成环
  4. 如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3
  5. 如果不会,要当前边,将该边的指向点加入到被选取的点中,重复2
  6. 当所有点都被选取,最小生成树就得到了

猜你喜欢

转载自juejin.im/post/7068623861775859720