最小生成树:Kruskal 和 Prim(Java 实现)

最小生成树

要求无向图(可以有重边)

Kruskal 算法

(加边法 -> 并查集法):

  1. 将图中所有按照从小到大顺序进行排序,放入最小堆
  2. 遍历每条边,对该边上两节点进行检查:如果处于不同点集,则合并,并将该边加入结果集;否则跳过该边

Prim 算法

(加点法):

数据结构

已访问点集:HashSet<Node>

待添加边集:PriorityQueue<Node>

结果边集:ArrayList<Edge>

算法

  1. 随机往已访问点集中加入图上的一个点,并将它关联的所有边加入待添加边集(用最小堆保存)
  2. 待添加边集中取出最小的边,即出队,检查该边指向的节点是否在已访问点集中:若不存在,则将该边加入结果边集,同时将指向的节点加入已访问点集,并将该点连接的所有边添加进待添加边集
  3. 重复第 2 步操作,直至待添加边集为空

思考:这两个算法都是贪心算法。每个算法都是尝试将最小边纳入结果集中。虽然 Kruskal 叫做加边法,但我觉得叫并查集法更为合适,因为每纳入一条边时,都会考虑边连接的两点是否在同一集合中。若处于同一集合则将集合合并。同样,Prim 算法虽然叫加点法,但它的本质仍然是加边,通过贪心加最小的边,只不过它每次只把一个邻接点而不是邻接点所处的集合进行合并。由此,两算法的区别,主要是图中顶点使用数据结构上的区别。Kruskal 用并查集保存,而 Prim 用 HashSet 保存。

注意:虽然 MST(Minimum Spanning Tree) 算法要求输入图为无向图,但由于用户误把有向图输入给算法,则该两种算法产生结果将不同:若在有向图上运行 Kruskal 算法,则仍能得到对应无向图的正确结果,因为它没有边指向的顶点这个概念;而让 Prim 算法在有向图上运行则不能得到对应无向图的正确结果。这一点在图的构建时应特别注意。

public List<Edge> kruskal() {
    Queue<Edge> priorityQueue = new PriorityQueue<>(((o1, o2) -> o1.weight - o2.weight));
    priorityQueue.addAll(this.edges);
    UnionFind<Node> unionFind = new UnionFind<>(new ArrayList<>(this.nodes.values()));  // 并查集保存顶点
    List<Edge> res = new ArrayList<>();

    while (!priorityQueue.isEmpty()) {
        Edge edge = priorityQueue.poll();
        Node from = edge.from;
        Node to = edge.to;

        boolean isTheSame = unionFind.find(from, to);
        if (!isTheSame) {
            unionFind.union(from, to);
            res.add(edge);
        }

    }
    return res;
}

public List<Edge> prim() {
    Set<Node> knownNodesSet = new HashSet<>();  // HashSet 保存顶点
    PriorityQueue<Edge> edgePriorityQueue = new PriorityQueue<>(((o1, o2) -> o1.weight - o2.weight));
    List<Edge> res = new ArrayList<>();

    // init: pick one node to knownNodesSet
    for (Node node : this.nodes.values()) {
        knownNodesSet.add(node);
        edgePriorityQueue.addAll(node.edges);

        while (!edgePriorityQueue.isEmpty()) {
            Edge edge = edgePriorityQueue.poll();

            // try to add toNode of this edge to set
            Node toNode = edge.to;
            if (!knownNodesSet.contains(toNode)) {
                knownNodesSet.add(toNode);
                edgePriorityQueue.addAll(toNode.edges);
                res.add(edge);
            }
        }

        break;
    }
    return res;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值