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(); // 关闭输入流
}
}