Prim算法求最小生成树,怎么用?

摘要:51.Acwing基础课第858题-简单-Prim算法求最小生成树 题目描述 给定一个 n 个点 m条边的无向图,图中可能存在重边和自环,边权可能为负数。 求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。 给定
51.Acwing基础课第858题-简单-Prim算法求最小生成树 题目描述 给定一个 n 个点 m条边的无向图,图中可能存在重边和自环,边权可能为负数。 求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。 给定一张边带权的无向图 G=(V,E),其中 V表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。 由 V𝑉 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G的最小生成树。 输入格式 第一行包含两个整数 n 和 m。 接下来 m行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。 输出格式 共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。 数据范围 1≤n≤500, 1≤m≤105 图中涉及边的边权的绝对值均不超过 10000。 输入样例: 4 5 1 2 1 1 3 2 1 4 3 2 3 2 3 4 4 输出样例: 6 代码: #include <iostream> #include <cstring> using namespace std; // 常量定义: // N:最大顶点数(题目约束510) // INF:无穷大(0x3f3f3f3f是常用的无穷大值,避免溢出且便于计算) const int N = 510, INF = 0x3f3f3f3f; int n, m; // n:顶点数,m:边数 int g[N][N]; // 邻接矩阵:g[i][j]表示顶点i到j的边权,初始为INF int dist[N]; // dist[i]:顶点i到当前最小生成树的最短距离 bool st[N]; // st[i]:标记顶点i是否已加入最小生成树 // Prim算法:求解无向图的最小生成树总权值,不连通则返回INF int prim() { // 初始化dist数组为无穷大:所有点初始时到生成树的距离都是无穷大 memset(dist, 0x3f, sizeof dist); int res = 0; // res:最小生成树的总权值 // 循环n次:依次将n个顶点加入生成树 for(int i = 0; i < n; i++) { int t = -1; // t:记录当前未加入生成树、且dist最小的顶点 // 第一步:找到未加入生成树的dist最小的顶点t for(int j = 1; j <= n; j++) { // 条件:j未加入生成树,且(t未初始化 或 j的dist比t更小) if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j; } // 第二步:判断图是否连通(非第一个顶点时) // 若dist[t]仍接近无穷大,说明没有边能连接到t,图不连通 if(i && dist[t] > INF/2) return INF; // 第三步:累加边权(第一个顶点无前置边,不加) if(i) res += dist[t]; st[t] = true; // 将t加入生成树 // 第四步:用顶点t更新其他顶点到生成树的最短距离 // 遍历所有顶点j,若t到j的边权比当前dist[j]更小,则更新 for(int j = 1; j <= n; j++) dist[j] = min(dist[j], g[t][j]); } return res; // 返回最小生成树总权值 } int main() { cin >> n >> m; // 输入顶点数n和边数m int a, b, c; // a,b:边的两个顶点;c:边权 // 初始化邻接矩阵为无穷大:表示初始时所有顶点间无连接 memset(g, 0x3f, sizeof g); // 输入m条边 while(m--) { cin >> a >> b >> c; // 处理重边:保留a-b之间权值最小的边(无向图双向赋值) g[a][b] = g[b][a] = min(g[a][b], c); } // 调用Prim算法,获取最小生成树总权值 int t = prim(); // 输出结果:若返回值接近无穷大,说明无最小生成树;否则输出总权值 if(t > INF/2) cout << "impossible" << endl; else cout << t << endl; return 0; } Prim 算法(朴素版) (1)核心思路 与迪杰斯特拉(Dijkstra)算法流程高度相似,核心是 “贪心选择当前离连通块最近的顶点”,逐步扩展连通块,直到覆盖所有顶点。 关键定义: 集合 S:当前已纳入生成树的顶点(连通块)。 距离数组dist[]:dist[i]表示顶点 i 到集合 S 的最短边权重(若 i 在 S 中,dist[i]=0;若无边连接,dist[i]=∞)。 (2)算法流程 初始化:所有顶点的dist[]设为∞,dist[任意起点]可设为 0(或默认从第一个顶点开始)。 迭代 n 次(n 为顶点数): a. 找到集合 S 外dist[]最小的顶点 t(“离连通块最近的顶点”)。 b. 若 t 的dist[]仍为∞,说明图不连通,无 MST。 c. 将 t 的dist[]累加到 MST 总权重(t 与 S 的最短边即为生成树的一条边)。 d. 将 t 加入集合 S(标记为已访问)。 e. 用 t 更新其他顶点到 S 的距离:若顶点 j 与 t 的边权重 < dist[j],则更新dist[j]。 (3)适用场景与时间复杂度 适用:稠密图(顶点数少、边数多,如 n=500,m=1e5)。 时间复杂度:O (n²)(n 次迭代,每次遍历 n 个顶点找最小值)。 优势:代码短,思路与 Dijkstra 一致,容易记忆。 (4)算法求最小生成树 内容详细总结 ①算法核心原理与前置定义 A.最小生成树(MST)基础定义 给定一个无向连通图,最小生成树是满足以下条件的边集合: 能将图中所有节点连通,且无环(符合树的基本性质,边数固定为「节点数 n-1」); 所有选中边的权重之和为全图最小。 B.Prim 算法核心贪心思想 Prim 算法采用 「逐步扩展连通块」 的贪心策略,核心逻辑为: 从任意一个起点(通常选 1 号节点)出发,构建初始连通块; 每一轮迭代,找到未加入连通块、且离当前连通块距离最短的节点,将其加入连通块; 用新加入的节点,更新剩余未加入节点到连通块的最短距离; 重复迭代直到所有节点都加入连通块,最终累加的距离和即为最小生成树的总权重;若迭代中无法找到可加入的节点,说明图不连通,无最小生成树。 C.算法核心变量 g[N][N]:邻接矩阵,存储节点间的边权(无边则为 INF=0x3f3f3f3f); dist[N]:存储未加入连通块的节点到当前连通块的最短距离; st[N]:标记节点是否已加入连通块(true= 已加入,false= 未加入); res:最小生成树的总权重。 ②Prim 算法逐轮迭代模拟 A.代码的核心执行顺序(必须严格遵守) prim()函数执行流程: 初始化 dist 全为 INF (0x3f3f3f3f),res=0 循环 n 次(i 从 0 到 n-1): a. 找未加入集合 st[j]=false 中,dist 最小的节点 t b. 若 i≠0 且 dist[t]==INF,说明图不连通,返回 INF c. 若 i≠0,将 dist[t] 累加到总权重 res d. 用节点 t 更新所有节点的 dist 数组:dist[j] = min(dist[j], g[t][j]) e. 标记 t 加入集合:st[t] = true 循环结束,返回 res B.逐轮模拟 算法共迭代 n=5 次,每次选择未加入连通块、且到连通块距离最短的节点,更新其他节点的距离,最终累加总权重。 初始状态(迭代前) dist = [INF, INF, INF, INF, INF](索引 0 未使用,1-5 对应节点 1-5); st = [false, false, false, false, false]; res = 0。 第 1 轮迭代(i=0,加入第 1 个节点:节点 1) 找最短距离节点:所有节点未加入,dist 均为 INF,默认选择节点 1(t=1)。 更新总权重:i=0 是第一次迭代,不加入权重(res 仍为 0)。 用节点 1 更新其他节点的距离: 节点 2:dist[2] = min(INF, g[1][2]=100) = 100; 节点 3:dist[3] = min(INF, g[1][3]=150) = 150; 节点 4:dist[4] = min(INF, g[1][4]=140) = 140; 节点 5:与节点 1 无边,dist[5] 仍为 INF。 标记节点 1 加入连通块:st[1] = true。 本轮结束后状态: dist = [INF, 100, 150, 140, INF]; st = [true, false, false, false, false]; res = 0。 第 2 轮迭代(i=1,加入第 2 个节点:节点 2) 找最短距离节点:未加入节点为 2、3、4、5,dist 最小的是节点 2(dist[2]=100,t=2)。 更新总权重:i≠0,res += dist[2] → res = 0 + 100 = 100。 用节点 2 更新其他节点的距离: 节点 4:dist[4] = min(140, g[2][4]=80) = 80; 节点 5:dist[5] = min(INF, g[2][5]=80) = 80; 节点 3:与节点 2 无边,dist[3] 仍为 150。 标记节点 2 加入连通块:st[2] = true。 本轮结束后状态: dist = [INF, 100, 150, 80, 80]; st = [true, true, false, false, false]; res = 100。 第 3 轮迭代(i=2,加入第 3 个节点:节点 4) 找最短距离节点:未加入节点为 3、4、5,dist 最小的是节点 4(dist[4]=80,t=4)。 更新总权重:res += dist[4] → res = 100 + 80 = 180。 用节点 4 更新其他节点的距离: 节点 3:dist[3] = min(150, g[4][3]=15) = 15; 节点 5:dist[5] = min(80, g[4][5]=10) = 10; 节点 1、2:已加入连通块,无需更新。 标记节点 4 加入连通块:st[4] = true。 本轮结束后状态: dist = [INF, 100, 15, 80, 10]; st = [true, true, false, true, false]; res = 180。 第 4 轮迭代(i=3,加入第 4 个节点:节点 5) 找最短距离节点:未加入节点为 3、5,dist 最小的是节点 5(dist[5]=10,t=5)。 更新总权重:res += dist[5] → res = 180 + 10 = 190。 用节点 5 更新其他节点的距离: 节点 3:与节点 5 无边,dist[3] 仍为 15; 其他节点:已加入连通块,无需更新。 标记节点 5 加入连通块:st[5] = true。 本轮结束后状态: dist = [INF, 100, 15, 80, 10]; st = [true, true, false, true, true]; res = 190。 第 5 轮迭代(i=4,加入第 5 个节点:节点 3) 找最短距离节点:未加入节点只有 3,dist[3]=15(t=3)。 更新总权重:res += dist[3] → res = 190 + 15 = 205。 用节点 3 更新其他节点的距离: 所有节点已加入连通块,无需更新。 标记节点 3 加入连通块:st[3] = true。 本轮结束后状态: 所有节点均加入连通块,迭代结束; 最终 res = 205。 ③最终结果 最小生成树总权重:205; 生成树的边(通过迭代过程反推):1-2、2-4、4-5、4-3(共 4 条边,符合 n-1=4 的树的性质); 图的连通性:所有节点均成功加入连通块,图是连通的,无 “impossible” 情况。 ④代码运行验证 将图的边权输入提供的代码,运行后输出结果为 205,与模拟结果完全一致,验证了算法的正确性。