51.Acwing基础课第858题-简单-Prim算法求最小生成树
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:已加入连通块,无需更新。
- 节点 3:
- 标记节点 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; - 其他节点:已加入连通块,无需更新。
- 节点 3:与节点 5 无边,
- 标记节点 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,与模拟结果完全一致,验证了算法的正确性。

浙公网安备 33010602011771号