通过Java理解Kruskal算法

今天,我将解析一段Java代码,该代码实现了Kruskal算法,用于在连通的无向图中找到最小生成树。

首先,我们来了解一些关键组件:

1. DisjointSet(不相交集):**

这是Kruskal算法中的辅助数据结构,用于管理不相交集的集合。

  • Find(查找):确定特定元素属于哪个集合,并返回该集合的代表成员。
  • Union(合并):将两个不同的集合合并为一个集合。

2. Kruskal:**

这个类包含实现Kruskal算法的方法。

  • worst_case(最坏情况):该方法演示如何使用预定义的边集合来利用算法。
  • Kruskal:该方法提供Kruskal算法的逻辑。

Kruskal算法的工作原理:

  1. 初始化:从每个顶点开始,构建一个由不相交的单节点树组成的森林。
  2. 排序:按照边的权重进行非递减排序。
  3. 添加边:对于每条边,按照非递减的顺序,如果边连接了两个不同的树,则将其添加到森林中,将两个树合并为一棵树。如果边连接了同一棵树的节点,则跳过它(以避免形成环)。
  4. 终止:算法在添加了(V-1)条边后结束,其中V是顶点的数量。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

// DisjointSet 类用于管理不相交集的集合
class DisjointSet {
    private int[] parent; // 存储每个节点的父节点
    private int[] rank; // 存储每个节点的秩(树的高度)

    // 构造函数,初始化 DisjointSet
    public DisjointSet(int n) {
        parent = new int[n + 1]; // 考虑从1开始的节点索引
        rank = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            parent[i] = i; // 每个节点的父节点初始化为自身
            rank[i] = 0; // 每个节点的秩初始化为0
        }
    }

    // 查找方法,返回节点 x 所在集合的代表元素
    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]); // 路径压缩
        }
        return parent[x];
    }

    // 合并方法,将 x 和 y 所在的集合合并,返回 true 如果合并成功,否则返回 false
    public boolean union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);

        if (rootX == rootY) {
            return false; // 已经在同一个集合中
        }

        // 将秩较低的树的根节点指向秩较高的树的根节点
        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++;
        }

        return true;
    }
}

public class Kruskal {
    // 示例图的邻接矩阵表示
    int[][] example_1 = new int[][] { { 0, 1, 3, Integer.MAX_VALUE, Integer.MAX_VALUE },
            { 1, 0, 3, 6, Integer.MAX_VALUE },
            { 3, 3, 0, 4, 2 },
            { Integer.MAX_VALUE, 6, 4, 0, 5 },
            { Integer.MAX_VALUE, Integer.MAX_VALUE, 2, 5, 0 } };

    // worst_case 方法演示了如何使用预定义的边集合来利用 Kruskal 算法
    public void worst_case() {
        int n = 5; // 顶点数
        int m = 7; // 边数

        Set<Edge> E = new HashSet<>(); // 存储边的集合

        // 添加边到集合
        E.add(new Edge(1, 2, 1));
        E.add(new Edge(1, 3, 3));
        E.add(new Edge(2, 3, 3));
        E.add(new Edge(2, 4, 6));
        E.add(new Edge(3, 4, 4));
        E.add(new Edge(3, 5, 2));
        E.add(new Edge(4, 5, 5));

        // 将 Set 转换为 List
        List<Edge> edgeList = new ArrayList<>(E);

        // 对 List 进行排序
        edgeList.sort(Comparator.comparingInt(Edge::getWeight));

        // 创建一个不相交集
        DisjointSet disjointSet = new DisjointSet(n);

        // 创建一个列表来存储 MST 的边
        List<Edge> mst = new ArrayList<>();

        // 按排序顺序遍历所有边
        for (Edge edge : edgeList) {
            // 如果边不形成环,将其添加到 MST 中
            if (disjointSet.union(edge.getU(), edge.getV())) {
                mst.add(edge);
            }
        }

        // 显示 MST 中的边
        System.out.println("Minimum Spanning Tree: ");

        for (Edge edge : mst) {
            System.out.println("From: " + edge.getU() + " To: " + edge.getV() + " Weight: " + edge.getWeight());
        }
    }

    // kruskal 方法提供了 Kruskal 算法的逻辑
    public Set<Edge> kruskal(int n, int m, Set<Edge> E) {
        int i, j;

        Set<Edge> P = new HashSet<>();
        Set<Edge> Q = new HashSet<>();

        Edge e = new Edge(0, 0, 0);

        Set<Edge> F = new HashSet<>();

        // 当 F 的大小小于 (n - 1) 时,继续循环
        while (F.size() < (n - 1)) {
            // 将 E 中的边移动到 P 中
            for (i = 0; i < m; i++) {
                e = E.iterator().next();
                E.remove(e);
                P.add(e);
            }

            // 从 P 中选择一条边,如果它不连接同一个顶点,将其添加到 F 中
            for (i = 0; i < m; i++) {
                e = P.iterator().next();
                P.remove(e);
                if (e.getU() != e.getV()) {
                    F.add(e);
                    break;
                } else {
                    Q.add(e);
                }
            }

            // 将 Q 中的边移回 P 中
            for (i = 0; i < m; i++) {
                e = Q.iterator().next();
                Q.remove(e);
                P.add(e);
            }
        }

        return F;
    }
}

代码深入解析

Kruskal类中:

  1. worst_case方法:该方法首先初始化一组边,然后根据它们的权重对这些边进行排序。然后,代码遍历这些边,如果它们不形成环,则将它们添加到最小生成树中。
  2. Kruskal方法:这个方法看起来更复杂,有多个循环和集合PQF

然而,提供的方法并不遵循传统的Kruskal算法。相反,它展示了通过重复迭代边来构建最小生成树的另一种方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

算法大师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值