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)算法流程

  1. 初始化:所有顶点的dist[]设为∞,dist[任意起点]可设为 0(或默认从第一个顶点开始)。

  2. 迭代 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)基础定义

给定一个无向连通图,最小生成树是满足以下条件的边集合:

  1. 能将图中所有节点连通,且无环(符合树的基本性质,边数固定为「节点数 n-1」);
  2. 所有选中边的权重之和为全图最小。
B.Prim 算法核心贪心思想

Prim 算法采用 「逐步扩展连通块」 的贪心策略,核心逻辑为:

  1. 从任意一个起点(通常选 1 号节点)出发,构建初始连通块;
  2. 每一轮迭代,找到未加入连通块、且离当前连通块距离最短的节点,将其加入连通块;
  3. 用新加入的节点,更新剩余未加入节点到连通块的最短距离;
  4. 重复迭代直到所有节点都加入连通块,最终累加的距离和即为最小生成树的总权重;若迭代中无法找到可加入的节点,说明图不连通,无最小生成树。
C.算法核心变量
  • g[N][N]:邻接矩阵,存储节点间的边权(无边则为 INF=0x3f3f3f3f);
  • dist[N]:存储未加入连通块的节点到当前连通块的最短距离;
  • st[N]:标记节点是否已加入连通块(true= 已加入,false= 未加入);
  • res:最小生成树的总权重。
②Prim 算法逐轮迭代模拟
A.代码的核心执行顺序(必须严格遵守)

prim()函数执行流程:

  1. 初始化 dist 全为 INF (0x3f3f3f3f),res=0

  2. 循环 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

  3. 循环结束,返回 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)

  1. 找最短距离节点:所有节点未加入,dist 均为 INF,默认选择节点 1(t=1)。

  2. 更新总权重i=0 是第一次迭代,不加入权重(res 仍为 0)。

  3. 用节点 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

  4. 标记节点 1 加入连通块st[1] = true

本轮结束后状态

  • dist = [INF, 100, 150, 140, INF]

    st = [true, false, false, false, false]

    res = 0

第 2 轮迭代(i=1,加入第 2 个节点:节点 2)

  1. 找最短距离节点:未加入节点为 2、3、4、5,dist 最小的是节点 2(dist[2]=100t=2)。

  2. 更新总权重i≠0res += dist[2]res = 0 + 100 = 100

  3. 用节点 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。

  4. 标记节点 2 加入连通块st[2] = true

本轮结束后状态

  • dist = [INF, 100, 150, 80, 80]
  • st = [true, true, false, false, false]
  • res = 100

第 3 轮迭代(i=2,加入第 3 个节点:节点 4)

  1. 找最短距离节点:未加入节点为 3、4、5,dist 最小的是节点 4(dist[4]=80t=4)。
  2. 更新总权重res += dist[4]res = 100 + 80 = 180
  3. 用节点 4 更新其他节点的距离:
    • 节点 3:dist[3] = min(150, g[4][3]=15) = 15
    • 节点 5:dist[5] = min(80, g[4][5]=10) = 10
    • 节点 1、2:已加入连通块,无需更新。
  4. 标记节点 4 加入连通块st[4] = true

本轮结束后状态

  • dist = [INF, 100, 15, 80, 10]
  • st = [true, true, false, true, false]
  • res = 180

第 4 轮迭代(i=3,加入第 4 个节点:节点 5)

  1. 找最短距离节点:未加入节点为 3、5,dist 最小的是节点 5(dist[5]=10t=5)。
  2. 更新总权重res += dist[5]res = 180 + 10 = 190
  3. 用节点 5 更新其他节点的距离:
    • 节点 3:与节点 5 无边,dist[3] 仍为 15;
    • 其他节点:已加入连通块,无需更新。
  4. 标记节点 5 加入连通块st[5] = true

本轮结束后状态

  • dist = [INF, 100, 15, 80, 10]
  • st = [true, true, false, true, true]
  • res = 190

第 5 轮迭代(i=4,加入第 5 个节点:节点 3)

  1. 找最短距离节点:未加入节点只有 3,dist[3]=15t=3)。
  2. 更新总权重res += dist[3]res = 190 + 15 = 205
  3. 用节点 3 更新其他节点的距离:
    • 所有节点已加入连通块,无需更新。
  4. 标记节点 3 加入连通块st[3] = true

本轮结束后状态

  • 所有节点均加入连通块,迭代结束;
  • 最终 res = 205
③最终结果
  1. 最小生成树总权重205
  2. 生成树的边(通过迭代过程反推):1-2、2-4、4-5、4-3(共 4 条边,符合 n-1=4 的树的性质);
  3. 图的连通性:所有节点均成功加入连通块,图是连通的,无 “impossible” 情况。
④代码运行验证

将图的边权输入提供的代码,运行后输出结果为 205,与模拟结果完全一致,验证了算法的正确性。

posted @ 2026-04-08 22:27  CodeMagicianT  阅读(1)  评论(0)    收藏  举报