news 2026/5/30 14:03:37

C语言图论:最小生成树算法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言图论:最小生成树算法

本文献给:
已掌握图论基础,希望理解如何在带权连通图中找到最小生成树的C语言学习者。本文将系统讲解两种经典的最小生成树算法。

你将学到:

  1. 最小生成树问题的定义与核心概念
  2. Prim算法:从顶点出发,逐步扩张生成树
  3. Kruskal算法:按边权排序,逐步合并连通分量
  4. 算法对比与实战应用

@toc

第一部分:问题定义与核心概念

1. 什么是最小生成树?

在带权连通无向图G=(V,E,w)G = (V, E, w)G=(V,E,w)中,w:E→Rw: E \rightarrow \mathbb{R}w:ER为边权函数。生成树GGG的一个子图,它是一棵包含GGG中所有顶点的树。最小生成树是所有生成树中边权之和最小的生成树。

关键术语:

  • 连通图:图中任意两个顶点都有路径相连。
  • 生成树:包含所有顶点的树,有∣V∣−1|V|-1V1条边。
  • 边权:通常为非负实数,表示距离、成本等。
  • 最小生成树性质:最小生成树不一定唯一,但边权之和唯一。

第二部分:图的存储(带权图)

与最短路径相同,我们使用带权图的存储方式。

#defineMAX_V100#defineINF0x3f3f3f3f// 表示"无穷大"的一个较大数值// 邻接矩阵(带权)typedefstruct{intmatrix[MAX_V][MAX_V];// 存储权值,INF表示无边intvertex_count;}GraphMatrixWeighted;voidinit_graph_weighted(GraphMatrixWeighted*g,intn){g->vertex_count=n;for(inti=0;i<n;i++){for(intj=0;j<n;j++){g->matrix[i][j]=(i==j)?0:INF;// 自身距离为0,但生成树中不考虑自环}}}voidadd_edge_weighted(GraphMatrixWeighted*g,intu,intv,intweight){if(u>=g->vertex_count||v>=g->vertex_count)return;g->matrix[u][v]=weight;g->matrix[v][u]=weight;// 无向图}

第三部分:Prim算法

1. 核心思想

贪心策略。从任意一个顶点开始,每次选择连接已选顶点集合和未选顶点集合的最小权值边,并将该边连接的未选顶点加入已选集合。直到所有顶点都被选中。

算法正确性依赖:最小生成树的局部最优选择性质。

2. 算法步骤(朴素O(∣V∣2)O(|V|^2)O(V2)实现)

  1. 初始化:选择一个起始顶点,设置visited[src]=1,初始化min_edge[v]为从起始顶点到v的边权(无边则为INF)。
  2. 循环|V|-1次:
    a. 从未访问的顶点中选择min_edge值最小的顶点u
    b. 将顶点u加入生成树(标记visited[u]=1),累加生成树权重。
    c. 更新min_edge数组:对于每个未访问的顶点v,如果matrix[u][v] < min_edge[v],则更新min_edge[v] = matrix[u][v]
  3. 循环结束,得到最小生成树的总权重。

3. C语言实现

// Prim算法 (邻接矩阵,朴素实现)intprim(GraphMatrixWeighted*g){intvisited[MAX_V]={0};intmin_edge[MAX_V];// 记录当前已选顶点集合到未选顶点的最小边权inttotal_weight=0;// 初始化,从顶点0开始visited[0]=1;for(inti=0;i<g->vertex_count;i++){min_edge[i]=g->matrix[0][i];}// 还需要选择 |V|-1 个顶点for(intcount=1;count<g->vertex_count;count++){// 找到未访问的顶点中 min_edge 最小的顶点intu=-1;intmin_weight=INF;for(intv=0;v<g->vertex_count;v++){if(!visited[v]&&min_edge[v]<min_weight){min_weight=min_edge[v];u=v;}}if(u==-1){// 图不连通,无法形成生成树return-1;}visited[u]=1;total_weight+=min_weight;// 更新 min_edge 数组for(intv=0;v<g->vertex_count;v++){if(!visited[v]&&g->matrix[u][v]<min_edge[v]){min_edge[v]=g->matrix[u][v];}}}returntotal_weight;}

算法复杂度:

  • 时间复杂度O(∣V∣2)O(|V|^2)O(V2),适合稠密图。
  • 空间复杂度O(∣V∣)O(|V|)O(V)
  • 优化方向:使用**优先队列(最小堆)**可将时间复杂度降为O((∣V∣+∣E∣)log⁡∣V∣)O((|V|+|E|) \log |V|)O((V+E)logV),适合稀疏图。

第四部分:Kruskal算法

1. 核心思想

贪心策略。将图中的所有边按权值从小到大排序,然后从权值最小的边开始,如果这条边连接的两个顶点不在同一个连通分量中,则选择这条边,并将两个连通分量合并。直到选择了∣V∣−1|V|-1V1条边。

算法正确性依赖:最小生成树的全局最优选择性质。

2. 算法步骤

  1. 将图中所有边按权值从小到大排序。
  2. 初始化一个并查集,每个顶点自成一个集合。
  3. 依次考察每条边(按权值从小到大):
    • 如果该边连接的两个顶点属于不同的集合(即不连通),则选择该边,并将两个集合合并。
    • 如果属于同一个集合,则选择这条边会形成环,因此舍弃。
  4. 当选择的边数达到∣V∣−1|V|-1V1时,算法结束。

3. 并查集(Disjoint Set)辅助数据结构

Kruskal算法需要并查集来快速判断两个顶点是否属于同一个连通分量。

// 并查集实现typedefstruct{intparent[MAX_V];intrank[MAX_V];}DisjointSet;voidmake_set(DisjointSet*ds,intn){for(inti=0;i<n;i++){ds->parent[i]=i;ds->rank[i]=0;}}intfind(DisjointSet*ds,intx){if(ds->parent[x]!=x){ds->parent[x]=find(ds,ds->parent[x]);// 路径压缩}returnds->parent[x];}voidunion_set(DisjointSet*ds,intx,inty){introot_x=find(ds,x);introot_y=find(ds,y);if(root_x!=root_y){// 按秩合并if(ds->rank[root_x]<ds->rank[root_y]){ds->parent[root_x]=root_y;}elseif(ds->rank[root_x]>ds->rank[root_y]){ds->parent[root_y]=root_x;}else{ds->parent[root_y]=root_x;ds->rank[root_x]++;}}}

4. Kruskal算法实现

为了便于操作,我们使用边列表来存储图。

// 边结构体typedefstruct{intu,v;intweight;}Edge;// 图(边列表)typedefstruct{Edge edges[MAX_V*MAX_V];intedge_count;intvertex_count;}GraphEdgeList;// 比较函数,用于排序intcompare_edges(constvoid*a,constvoid*b){Edge*edge_a=(Edge*)a;Edge*edge_b=(Edge*)b;returnedge_a->weight-edge_b->weight;}// Kruskal算法intkruskal(GraphEdgeList*g){// 1. 将边按权值排序qsort(g->edges,g->edge_count,sizeof(Edge),compare_edges);// 2. 初始化并查集DisjointSet ds;make_set(&ds,g->vertex_count);inttotal_weight=0;intedges_selected=0;// 3. 遍历每条边for(inti=0;i<g->edge_count;i++){intu=g->edges[i].u;intv=g->edges[i].v;intweight=g->edges[i].weight;// 如果u和v不在同一个集合中if(find(&ds,u)!=find(&ds,v)){union_set(&ds,u,v);total_weight+=weight;edges_selected++;if(edges_selected==g->vertex_count-1){break;}}}// 如果选出的边数不足 |V|-1,则图不连通if(edges_selected!=g->vertex_count-1){return-1;}returntotal_weight;}

算法复杂度:

  • 时间复杂度O(∣E∣log⁡∣E∣)O(|E| \log |E|)O(ElogE),主要开销在排序。
  • 空间复杂度O(∣V∣+∣E∣)O(|V| + |E|)O(V+E)

第五部分:总结与对比

算法对比表

特性Prim算法Kruskal算法
适用图类型连通图(通常稠密图)连通图(通常稀疏图)
核心思想从顶点出发,逐步扩张生成树按边权排序,逐步合并连通分量
时间复杂度O(∣V∣2)O(|V|^2)O(V2)O((∣V∣+∣E∣)log⁡∣V∣)O((|V|+|E|)\log|V|)O((V+E)logV)O(∣E∣log⁡∣E∣)O(|E|\log|E|)O(ElogE)
空间复杂度O(∣V∣)O(|V|)O(V)O(∣V∣+∣E∣)O(|V|+|E|)O(V+E)
优点适合稠密图,实现简单适合稀疏图,边排序后操作简单
缺点稠密图时效率高,稀疏图不如Kruskal稀疏图时效率高,稠密图排序开销大
存储结构邻接矩阵或邻接表边列表

选择指南

  1. 稠密图:优先使用Prim算法(朴素实现即可)。
  2. 稀疏图:优先使用Kruskal算法,因为其时间复杂度与边数有关,排序开销相对较小。
  3. 图存储结构:如果图本身是边列表形式,使用Kruskal算法更方便;如果是邻接矩阵或邻接表,Prim算法可能更方便。

觉得文章有帮助?别忘了:
👍 点赞 👍- 给我一点鼓励
⭐ 收藏 ⭐- 方便以后查看
🔔 关注 🔔- 获取更新通知


标签:#C语言#图论#最小生成树#Prim算法#Kruskal算法#算法

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/30 14:53:50

Django 学生成绩管理系统

项目概况 这是一个基于Django框架开发的学生成绩管理系统,旨在提供简单高效的成绩管理解决方案,适用于学校、培训机构等教育场景。 技术栈 - 后端 : Django 5.0.6 + SQLite - 前端 : Bootstrap 5 + Django Template Language - 核心依赖 : django-widget-tweaks 核心功能模…

作者头像 李华
网站建设 2026/5/30 14:19:40

13、UNIX用户管理全解析

UNIX用户管理全解析 1. 用户管理概述 用户管理几乎涉及系统管理各个领域的技能,工作核心围绕机器用户展开。理想的用户管理是不被用户察觉的,因为用户间接为系统运行付费,所以与系统的深入交互才得以实现。用户管理主要涉及用户ID的管理操作,包括添加、删除、修改、移动、…

作者头像 李华
网站建设 2026/5/29 2:30:13

动态规划01背包问题

动态规划:01背包问题 情景 现在有一个容量有限的背包(比如能装10公斤的东西)&#xff0c;现在有价值不同&#xff0c;重量也不同的几件物品&#xff0c;我们要怎样装才能让这个背包尽可能的装的价值最高 这就是为什么这个问题叫01背包问题&#xff0c;每个物品只有两种状态,放入…

作者头像 李华
网站建设 2026/5/30 9:36:04

WinForm DataGridView:单元格类型与高频绘制案例

目录 一、前置准备 二、DataGridView 常用单元格类型&#xff08;基础必掌握&#xff09; 1. 文本框单元格&#xff08;DataGridViewTextBoxColumn&#xff09; 2. 复选框单元格&#xff08;DataGridViewCheckBoxColumn&#xff09; 3. 下拉框单元格&#xff08;DataGridV…

作者头像 李华
网站建设 2026/5/30 11:57:39

java计算机毕业设计社区志愿者服务系统 智慧社区公益志愿协同平台 基层志愿者数字化运营管理系统

计算机毕业设计社区志愿者服务系统38q2o9 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。当“志愿红”成为社区里最温暖的底色&#xff0c;传统的人工登记、微信群接龙、纸质工时…

作者头像 李华
网站建设 2026/5/30 13:12:59

考核算法题纠错

考核题算法题纠错 打家劫舍int rob(int* nums, int numsSize) {if (numsSize 0) return 0;if (numsSize 1) return nums[0];int prev_prev nums[0];int prev nums[0] > nums[1] ? nums[0] : nums[1];for (int i 2; i < numsSize; i) {int current prev > (prev…

作者头像 李华