导读:本期聚焦于小伙伴创作的《C++如何用邻接矩阵和贪心策略实现Prim最小生成树算法?》,敬请观看详情。Prim算法本质是从任意一个顶点出发,每次从候选边里挑一条权重最小且不会形成环的边加入生成树,直到覆盖所有顶点。用邻接矩阵存图时,取最小边只需扫描数组,逻辑直观但空间占O(V^2)。实战里贪心策略靠一个lowcost数组记录各点到已选集合的最短距离,vis数组标记是否入树。相较邻接表,矩阵在稠密图更省心,稀疏图则偏浪费。下面给出完整C++代码与逐步拆解,说明初始化、选点、松弛三步怎么写才不易出错,并分析时间复杂度与常见越界问题。

Prim算法是解决带权无向图最小生成树问题的经典贪心算法。其核心思想是维护两个顶点集合:已加入生成树的集合U和未加入的集合V-U,每一步都从连接U与V-U的所有边中,选出权重最小的一条,将对应顶点纳入U。使用邻接矩阵存储图结构,能够用二维数组直观地表达任意两点间的边权,非常适合教学与稠密图场景。

C++如何用邻接矩阵和贪心策略实现Prim最小生成树算法?

一、邻接矩阵的定义与图初始化

在C++中,邻接矩阵通常用二维vector或原生数组表示。假设图有n个顶点,矩阵graph[i][j]存放顶点i到j的边权,若两点不直接相连则设为无穷大(如INT_MAX)。这种表示法让查询边权变为O(1)操作,但建图本身需要O(n^2)空间。

初始化时,我们要把对角线设为0,其他位置按输入或随机数据填充。下面代码展示了一个简单的矩阵构建方式,并演示了如何把不连通情况标记为大数,避免后续比较出错。

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int main() {
    int n = 5;
    // 用 vector 构建邻接矩阵
    vector<vector<int>> graph(n, vector<int>(n, INT_MAX));
    for (int i = 0; i < n; i++) {
        graph[i][i] = 0; // 自己到自己的距离为0
    }
    // 假设输入边:0-1权2,0-3权6,1-2权3,1-3权8,1-4权5,2-4权7,3-4权9
    graph[0][1] = graph[1][0] = 2;
    graph[0][3] = graph[3][0] = 6;
    graph[1][2] = graph[2][1] = 3;
    graph[1][3] = graph[3][1] = 8;
    graph[1][4] = graph[4][1] = 5;
    graph[2][4] = graph[4][2] = 7;
    graph[3][4] = graph[4][3] = 9;
    return 0;
}

二、Prim算法的贪心策略与核心数组

Prim的贪心体现在“每一步都选当前可选的最小边”。为实现这一点,我们引入两个辅助数组:lowcost记录每个未入树顶点到已入树集合的最小边权,vis标记顶点是否已在生成树中。初始时,从顶点0开始,把lowcost[i]设为graph[0][i]vis[0]置为true。

随后进行n-1轮循环:在每一轮中,扫描lowcost找到最小值对应的顶点u(要求vis[u]为false),将其加入生成树,然后以u为中介去“松弛”其他顶点——若graph[u][v]比当前lowcost[v]更小,就更新它。这种策略保证每次加入的边都是连接两集合的最小权重边,不会形成环。

void prim(vector<vector<int>>& graph, int n) {
    vector<bool> vis(n, false);
    vector<int> lowcost(n, INT_MAX);
    // 从顶点0开始
    vis[0] = true;
    for (int i = 0; i < n; i++) {
        lowcost[i] = graph[0][i];
    }
    int total = 0;
    for (int cnt = 1; cnt < n; cnt++) {
        int min_val = INT_MAX;
        int u = -1;
        for (int i = 0; i < n; i++) {
            if (!vis[i] && lowcost[i] < min_val) {
                min_val = lowcost[i];
                u = i;
            }
        }
        if (u == -1) {
            cout << "图不连通,无法生成树" << endl;
            return;
        }
        vis[u] = true;
        total += min_val;
        cout << "选择顶点 " << u << " 边权 " << min_val << endl;
        // 松弛操作
        for (int v = 0; v < n; v++) {
            if (!vis[v] && graph[u][v] < lowcost[v]) {
                lowcost[v] = graph[u][v];
            }
        }
    }
    cout << "最小生成树总权值: " << total << endl;
}

三、完整可运行示例与输出分析

将前面的初始化与prim函数组合,即可得到一份完整程序。以下代码在main中建图后直接调用prim,输出每次选中的顶点与累计权值,帮助理解贪心过程如何一步步覆盖全部节点。

对于上文的5顶点图,算法会依次选入顶点1(权2)、顶点2(权3)、顶点4(权5)、顶点3(权6),总权值为16。注意当图不连通时,u会保持-1,程序应及时退出并提示,否则会错误地把INT_MAX加入统计。

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

void prim(vector<vector<int>>& graph, int n) {
    vector<bool> vis(n, false);
    vector<int> lowcost(n, INT_MAX);
    vis[0] = true;
    for (int i = 0; i < n; i++) {
        lowcost[i] = graph[0][i];
    }
    int total = 0;
    for (int cnt = 1; cnt < n; cnt++) {
        int min_val = INT_MAX;
        int u = -1;
        for (int i = 0; i < n; i++) {
            if (!vis[i] && lowcost[i] < min_val) {
                min_val = lowcost[i];
                u = i;
            }
        }
        if (u == -1) {
            cout << "图不连通,无法生成树" << endl;
            return;
        }
        vis[u] = true;
        total += min_val;
        cout << "选择顶点 " << u << " 边权 " << min_val << endl;
        for (int v = 0; v < n; v++) {
            if (!vis[v] && graph[u][v] < lowcost[v]) {
                lowcost[v] = graph[u][v];
            }
        }
    }
    cout << "最小生成树总权值: " << total << endl;
}

int main() {
    int n = 5;
    vector<vector<int>> graph(n, vector<int>(n, INT_MAX));
    for (int i = 0; i < n; i++) graph[i][i] = 0;
    graph[0][1] = graph[1][0] = 2;
    graph[0][3] = graph[3][0] = 6;
    graph[1][2] = graph[2][1] = 3;
    graph[1][3] = graph[3][1] = 8;
    graph[1][4] = graph[4][1] = 5;
    graph[2][4] = graph[4][2] = 7;
    graph[3][4] = graph[4][3] = 9;
    prim(graph, n);
    return 0;
}

四、复杂度与实战注意事项

时间复杂度方面,外层循环n-1次,内层找最小和松弛各扫n个顶点,总体为O(n^2)。在顶点数不超过几千的稠密图中,这种实现比用优先队列的O(E log V)版本更简单且常数小。但若图很稀疏,邻接表加小根堆会更省时间。

实战中容易踩的坑包括:忘记把INT_MAX的边在松弛时参与比较导致溢出;用int存总权值在边权很大时越界;以及误把矩阵中0当成有效边(应区分自环0与初始化0)。建议将无穷大设为INT_MAX/2,避免加法溢出,并用long long累计权值。

存储方式时间复杂度适用场景
邻接矩阵+数组扫描O(V^2)稠密图、教学演示
邻接表+优先队列O(E log V)稀疏图、大规模数据

五、小结

用C++以邻接矩阵实现Prim算法,重点在于理解贪心选择靠lowcost数组落地,以及松弛步骤如何更新候选边。代码虽短,但边界处理决定了程序健壮性。掌握这一基础版本后,可进一步改写为堆优化形式应对不同数据规模。

C++Prim算法邻接矩阵修改时间:2026-08-07 10:15:24

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。