最小生成树算法

最小生成树问题描述

最小生成树(Minimum Spanning Tree, MST)问题是图论中的一个经典问题,它在各种网络设计领域都有广泛的应用。最小生成树指的是在一个加权连通图中,找到一个边的子集,使得这些边构成的树包含图中的所有顶点,并且边的权重之和最小。
给定连通图,求最小生成树:
最小生成树问题示意图
该连通图的一个最小生成树:
最小生成树

Prim求最小生成树

prim算法是基于贪心的,他的核心思想:

  • 选择一个距离集合最近的,加入集合
  • 更新其他点到集合的距离
  • 回到步骤1

可以看到算法的过程就是遍历所有的点,每次选择一个距离集合最近的点,加入集合,并更新其他点到集合的距离,因此是两层循环,时间复杂度是O(n^2)。

示例图演示起来太麻烦了,可以自己根据prim算法的步骤演示一遍

示例代码:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n, m; // 连通图中的点 以及边的个数
        n = in.nextInt();
        m = in.nextInt();
        int[][] graph = new int[n + 1][n + 1]; // 邻接矩阵存储图
        int[] dist = new int[n + 1]; // 距离集合的距离
        int[] st = new int[n + 1]; // 是否在集合中
        Arrays.fill(dist, Integer.MAX_VALUE);
        for (int i = 0; i <= n; i++) Arrays.fill(graph[i], Integer.MAX_VALUE);

        for (int i = 0; i < m; i++) {
            int a, b, c;
            a = in.nextInt();
            b = in.nextInt();
            c = in.nextInt();
            graph[a][b] = graph[b][a] = Math.min(graph[a][b], c);
        }
        int ans = 0; // 最小生成树的权重之和
        boolean flag = true;
        // 加入n次
        for (int i = 0; i < n; i++) {
            int t = -1;
            for (int j = 1; j <= n; j++) {
                if (st[j] == 0 && (t == -1 || dist[j] < dist[t])) t = j;
            }
            if (i != 0 && dist[t] == Integer.MAX_VALUE) {
                flag = false;;
                break;
            }
            if (i != 0) ans += dist[t];
            st[t] = 1;
            for (int j = 1; j <= n; j++) {
                dist[j] = Math.min(dist[j], graph[t][j]);
            }
        }
        if (flag) {
            System.out.println(ans);
        } else {
            System.out.println("impossible");
        }
    }
}

Kruskal求最小生成树

该算法也是基于贪心的,基本思想是从权重最小的边开始依次加入到集合中。
为了避免出现重边,需要用到并查集来判断两个点是否已经在树中了。

示例代码:

import java.util.*;

class Main{
    public static void main(String[] args) {
        int n, m;
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        List<int[]> edges = new ArrayList<>();
        for (int i = 0; i < m; i++) {
            int a, b, c;
            a = in.nextInt();
            b = in.nextInt();
            c = in.nextInt();
            edges.add(new int[]{a, b, c});
        }
        Collections.sort(edges, (a, b) ->(a[2] - b[2]));
        int[] father = new int[n + 1];
        for (int i = 0; i <= n; i++) father[i] = i;
        int cnt = 0; // 构成最小生成树时 cnt = n - 1
        int res = 0; // 权重
        for (int i = 0; i < edges.size(); i++) {
            int[] cur = edges.get(i);
            int a = cur[0];
            int b = cur[1];
            int c = cur[2];
            int fathera = find(a, father);
            int fatherb = find(b, father);
            if (fathera != fatherb) {
                cnt++;
                res += c;
                father[fathera] = father[fatherb];
            }
        }
        if (cnt != n - 1) {
            System.out.println("impossible");
        } else {
            System.out.println(res);
        }
    }
    public static int find(int u, int[] father) {
        if (father[u] != u) {
            father[u] = find(father[u], father);
        }
        return father[u];
    }
}

把连通图看作一个点集合,kruskal算法的过程就是往集合中加边的过程。

总结

prim遍历点,kruskal遍历边,所以一个适合稠密图,一个适合稀疏图。
其实这两个算法不难,关键是做题的时候能不能想到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值