day53 代码随想录算法训练营 图论专题7

1 今日打卡

prim算法 53. 寻宝(第七期模拟笔试)

kruskal算法 53. 寻宝(第七期模拟笔试)

2 prim算法

2.1 思路

Prim 算法的本质是「贪心算法」,类比成 "扩圈":

初始化:选一个起点(代码中默认从顶点 1 开始),把它加入生成树。

迭代:每次从不在生成树中的顶点里,找一个「到生成树距离最近」的顶点,加入生成树。

更新:将新加入的顶点作为中介,更新其他未加入顶点到生成树的最短距离。

终止:当所有顶点都加入生成树时,所有选中的边的权值和就是最小生成树的总权值。

邻接矩阵初始化:用Integer.MAX_VALUE表示 "无直接边",需注意后续更新时避免溢出(比如判断grid[cur][j] != Integer.MAX_VALUE);

距离数组初始值:设为 10001 是因为题目中边权通常不超过 10000,保证初始值大于所有合法边权;

循环次数:for (int i = 1; i < v; i++) 循环 v-1 次,因为最小生成树有 v 个顶点时,恰好需要 v-1 条边;

起点隐含逻辑:代码未显式设置minDist[1] = 0,但第一次循环会选中顶点 1(因为初始值 10001 <Integer.MAX_VALUE),属于 "巧合正确",规范写法建议补充minDist[1] = 0。

2.2 实现代码

java 复制代码
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // 1. 输入处理与数据初始化
        Scanner sc = new Scanner(System.in);
        // v:顶点数量(题目中顶点编号从1开始),e:边的数量
        int v = sc.nextInt(), e = sc.nextInt();
        
        // 邻接矩阵存储无向图:grid[from][to] 表示顶点from到to的边权
        // 初始化所有边权为Integer.MAX_VALUE(表示初始时两点间无直接边)
        int[][] grid = new int[v + 1][v + 1];
        for (int i = 0; i <= v; i++) {
            Arrays.fill(grid[i], Integer.MAX_VALUE);
        }
        
        // minDist[j]:顶点j到「当前最小生成树」的最短距离
        // 初始值设为10001(题目中边权最大值通常为10000,保证初始值大于所有合法边权)
        int[] minDist = new int[v + 1];
        Arrays.fill(minDist, 10001);
        
        // isInTree[j]:标记顶点j是否已加入最小生成树,初始为false(未加入)
        boolean[] isInTree = new boolean[v + 1];
        
        // 2. 读取所有边的信息(无向图,双向赋值)
        for (int i = 0; i < e; i++) {
            int from = sc.nextInt();   // 边的起点
            int to = sc.nextInt();     // 边的终点
            int val = sc.nextInt();    // 边的权值
            grid[from][to] = val;      // 无向图:起点→终点的权值
            grid[to][from] = val;      // 无向图:终点→起点的权值
        }

        // 3. Prim算法核心逻辑:构建最小生成树(需选v-1条边,循环v-1次)
        for (int i = 1; i < v; i++) {
            // 3.1 找「未加入生成树」且「到生成树距离最近」的顶点cur
            int minVal = Integer.MAX_VALUE;  // 记录当前找到的最短距离
            int cur = -1;                    // 记录当前选中的顶点(初始为无效值)
            for (int j = 1; j <= v; j++) {
                // 条件:顶点j未加入树 + 顶点j到树的距离 < 当前最短距离
                if (!isInTree[j] && minDist[j] < minVal) {
                    minVal = minDist[j];  // 更新最短距离
                    cur = j;              // 更新选中的顶点
                }
            }
            
            // 3.2 将选中的顶点cur加入最小生成树
            isInTree[cur] = true;
            
            // 3.3 以cur为中介,更新「未加入树的顶点」到生成树的最短距离
            for (int j = 1; j <= v; j++) {
                // 条件:顶点j未加入树 + cur到j有直接边(权值不是无穷大) + 这条边权更小
                if (!isInTree[j] && grid[cur][j] < minDist[j]) {
                    minDist[j] = grid[cur][j];  // 更新顶点j到树的最短距离
                }
            }
        }

        // 4. 计算最小生成树的总权值(顶点1是起点,累加2~v的最短距离)
        int res = 0;
        for (int i = 2; i <= v; i++) {
            res += minDist[i];
        }
        // 输出结果
        System.out.println(res);
        
        sc.close();  // 关闭输入流
    }
}

3 kruskal算法

3.1 思路

核心思想:将所有边按权值从小到大排序,依次尝试将边加入生成树,若加入该边不会形成环,则保留;若形成环则跳过,直到选够 v-1 条边(v 为顶点数),最终所有保留的边构成最小生成树。

关键支撑:并查集(Disjoint Set)

判断 "加入边是否形成环" 的核心工具是并查集:

初始化:每个顶点自成一个集合(父节点是自己)。

查找(find):找顶点所属集合的根节点(路径压缩优化,提升效率)。

合并(join):将两个顶点所在的集合合并。

判断是否同集(isSame):若两个顶点的根节点相同,说明在同一集合,加入边会形成环;否则不会。

Kruskal 算法完整执行逻辑(分 4 步)

数据准备:读取顶点数、边数,存储所有边(记录起点、终点、权值)。

边排序:将所有边按权值升序排列(贪心选最小权值边)。

并查集初始化:每个顶点初始属于独立集合。

遍历选边:

按排序后的顺序遍历每条边;

若边的两个顶点不在同一集合(无环),则将边加入生成树(累加权值),并合并两个顶点的集合;

若在同一集合(有环),则跳过该边;

直到选够 v-1 条边(可提前终止,优化效率)。

3.2 实现代码

java 复制代码
import java.util.*;

public class Main {
    // 并查集(Disjoint Set)类:用于判断边是否形成环、合并顶点集合
    static class disJoint {
        private int[] father;  // father[i]表示顶点i的父节点,核心数组
        
        // 并查集初始化:每个顶点的父节点是自己(自成一个集合)
        public disJoint(int n) {
            father = new int[n];
            for (int i = 0; i < n; i++) {
                father[i] = i;
            }
        }
        
        // 查找顶点a的根节点(路径压缩优化:让节点直接指向根,减少后续查找次数)
        public int find(int a) {
            if (a == father[a]) return a;  // 找到根节点,直接返回
            return father[a] = find(father[a]);  // 路径压缩:将当前节点的父节点设为根节点
        }
        
        // 合并顶点a和b所在的集合(按根节点合并)
        public void join(int a, int b) {
            a = find(a);  // 找a的根节点
            b = find(b);  // 找b的根节点
            if (a == b) return;  // 已在同一集合,无需合并
            father[b] = a;       // 将b的根节点指向a的根节点,完成合并
            return;
        }
        
        // 判断顶点a和b是否在同一集合(是否连通)
        public boolean isSame(int a, int b) {
            return find(a) == find(b);
        }
    }

    // 边(Edge)类:存储一条边的起点(l)、终点(r)、权值(val)
    static class edge {
        private int l, r, val;  // l:起点,r:终点,val:边权
        
        // 边的构造方法:初始化起点、终点、权值
        public edge(int l, int r, int val) {
            this.l = l;
            this.r = r;
            this.val = val;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 输入顶点数v、边数e(顶点编号从1开始)
        int v = sc.nextInt(), e = sc.nextInt();
        
        // 初始化并查集:容量为v+1(适配顶点编号1~v)
        disJoint dj = new disJoint(v + 1);
        
        // 存储所有边的列表
        List<edge> edges = new ArrayList<>();
        
        // 读取e条边的信息,加入列表
        for (int i = 0; i < e; i++) {
            int l = sc.nextInt(), r = sc.nextInt(), val = sc.nextInt();
            edges.add(new edge(l, r, val));
        }
        
        // 核心步骤:将所有边按权值升序排序(贪心选最小边)
        edges.sort((e1, e2) -> e1.val - e2.val);
        
        int res = 0;  // 存储最小生成树的总权值
        
        // 遍历排序后的边,依次尝试加入生成树
        for (edge cur : edges) {
            // 关键判断:当前边的两个顶点是否在同一集合(是否会形成环)
            if (!dj.isSame(cur.l, cur.r)) {
                res += cur.val;          // 无环,累加边权到结果
                dj.join(cur.l, cur.r);   // 合并两个顶点的集合(加入这条边)
            }
            // (可选优化:若已选够v-1条边,可break提前终止循环)
            // if (count == v-1) break;
        }
        
        // 输出最小生成树的总权值
        System.out.println(res);
        sc.close();  // 关闭输入流
    }
}
相关推荐
_日拱一卒2 小时前
LeetCode(力扣):二叉树的后序遍历
算法·leetcode·职场和发展
Emilin Amy2 小时前
【ROS】机器人的速度/角度/力矩控制是如何实现的
c++·算法·控制·ros1/2
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章36-骨架提取
图像处理·人工智能·opencv·算法·计算机视觉
CoovallyAIHub2 小时前
RF-DETR:最近一个月迭代 5 个版本的实时检测+分割模型
深度学习·算法·计算机视觉
Frostnova丶2 小时前
LeetCode 1878. 矩阵中最大的三个菱形和
算法·leetcode·矩阵
m0_662577972 小时前
C++中的享元模式实战
开发语言·c++·算法
Storynone2 小时前
【Day】LeetCode:134. 加油站,135. 分发糖果,860. 柠檬水找零,406. 根据身高重建队列
python·算法·leetcode
喵喵蒻葉睦2 小时前
力扣 hot100 和为K的子数组 哈希&前缀和
java·数据结构·算法·leetcode·前缀和·哈希算法
jing-ya2 小时前
day52 图论part4
数据结构·算法·图论