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,与模拟结果完全一致,验证了算法的正确性。
