C++图论全面解析:从基础概念到算法实践
目录
一、图的基本概念与分类
1.1 什么是图?
1.2 图的分类体系
二、图的存储结构详解
2.1 邻接矩阵(Adjacency Matrix)
2.2 邻接表(Adjacency List)
2.3 边集数组(Edge List)
2.4 链式前向星
三、图论核心算法深度解析
3.1 深度优先搜索(DFS)算法
3.2 广度优先搜索(BFS)算法
3.3 Dijkstra最短路径算法
3.4 Floyd-Warshall算法
3.5 拓扑排序算法
四、图论在现实中的应用
五、洛谷图论入门题目精选
六、图论学习建议
一、图的基本概念与分类
1.1 什么是图?
图(Graph)是由顶点(Vertex)和边(Edge)组成的非线性数据结构,用于表示对象之间的复杂关系。在计算机科学中,图被广泛应用于社交网络、交通系统、网络拓扑等场景。
图的基本组成元素:
-
顶点(Node/Vertex):表示实体对象
-
边(Edge):表示顶点之间的关系
-
权重(Weight):边可以带有权值,表示关系的强度或成本
1.2 图的分类体系
(1)按方向性分类
无向图(Undirected Graph):
-
边没有方向性
-
表示双向关系
-
邻接矩阵对称
-
示例:社交网络中的好友关系
有向图(Directed Graph/Digraph):
-
边有明确方向
-
表示单向关系
-
邻接矩阵不对称
-
示例:网页链接关系
(2)按权重分类
无权图:
-
边没有权值
-
只表示连接关系
-
示例:迷宫路径
带权图(Weighted Graph):
-
边有权值
-
表示关系的强度或成本
-
示例:城市间距离
(3)按连通性分类
连通图(Connected Graph):
-
任意两顶点间都有路径
-
无向图的特殊情况
强连通图(Strongly Connected Graph):
-
有向图中任意两顶点双向可达
-
示例:某些通信网络
非连通图(Disconnected Graph):
-
存在孤立顶点或子图
-
示例:岛屿分布图
(4)特殊图类型
树(Tree):
-
无环连通无向图
-
n个顶点有n-1条边
-
示例:组织结构图
二分图(Bipartite Graph):
-
顶点可分为两个不相交集合
-
所有边连接两个集合中的顶点
-
示例:求职者与职位匹配
完全图(Complete Graph):
-
每对顶点之间都有边相连
-
边数为n(n-1)/2(无向图)
-
示例:小型社交圈
二、图的存储结构详解
2.1 邻接矩阵(Adjacency Matrix)
实现原理:
使用二维数组matrix[u][v]
表示顶点u和v之间的边关系。
cpp
const int MAXN = 1000;
int adjMatrix[MAXN][MAXN]; // 邻接矩阵// 初始化
void initMatrix() {memset(adjMatrix, 0, sizeof(adjMatrix));
}// 添加边
void addEdge(int u, int v, int w = 1) {adjMatrix[u][v] = w;// 无向图需要对称添加// adjMatrix[v][u] = w;
}
性能分析:
-
空间复杂度:O(V²)
-
查询边存在:O(1)
-
遍历邻接点:O(V)
适用场景:
-
稠密图(边数接近顶点数平方)
-
需要频繁查询边存在性
-
图规模不大(V<1000)
2.2 邻接表(Adjacency List)
实现原理:
为每个顶点维护一个链表,存储其邻接顶点。
cpp
#include <vector>
using namespace std;const int MAXN = 100000;
vector<int> adjList[MAXN]; // 无权图邻接表// 带权图邻接表
struct Edge {int to, weight;
};
vector<Edge> adjListW[MAXN];// 添加边
void addEdge(int u, int v, int w = 1) {adjList[u].push_back(v);// 无向图需要双向添加// adjList[v].push_back(u);// 带权图添加// adjListW[u].push_back({v, w});
}
性能分析:
-
空间复杂度:O(V+E)
-
查询边存在:O(degree(u))
-
遍历邻接点:O(degree(u))
适用场景:
-
稀疏图
-
大规模图(V>10000)
-
需要频繁遍历邻接点
2.3 边集数组(Edge List)
实现原理:
简单存储所有边的集合。
cpp
struct Edge {int from, to, weight;
};
vector<Edge> edges;
适用场景:
-
Kruskal等需要处理所有边的算法
-
对存储顺序有特殊要求的场景
2.4 链式前向星
实现原理:
使用数组模拟链表,结合了邻接表和边集数组的优点。
cpp
const int MAXE = 100000;
struct Edge {int to, w, next;
} edges[MAXE];
int head[MAXN], edgeCnt;void init() {memset(head, -1, sizeof(head));edgeCnt = 0;
}void addEdge(int u, int v, int w) {edges[edgeCnt] = {v, w, head[u]};head[u] = edgeCnt++;
}
优势:
-
内存连续,缓存友好
-
适合竞赛编程
三、图论核心算法深度解析
3.1 深度优先搜索(DFS)算法
算法思想:
尽可能深地探索图的分支,直到无法继续前进才回溯。
实现代码:
cpp
vector<bool> visited(MAXN, false);void dfs(int u) {visited[u] = true;// 处理顶点ufor (int v : adjList[u]) {if (!visited[v]) {dfs(v);}}
}
应用场景:
-
连通分量检测
-
拓扑排序(有向无环图)
-
寻找图中环路
-
解决迷宫问题
时间复杂度:O(V+E)
3.2 广度优先搜索(BFS)算法
算法思想:
逐层扩展探索,先访问离起点近的顶点。
实现代码:
cpp
void bfs(int start) {queue<int> q;vector<bool> visited(MAXN, false);q.push(start);visited[start] = true;while (!q.empty()) {int u = q.front();q.pop();// 处理顶点ufor (int v : adjList[u]) {if (!visited[v]) {visited[v] = true;q.push(v);}}}
}
应用场景:
-
无权图最短路径
-
社交网络中的"好友推荐"
-
网页爬虫
-
网络广播
时间复杂度:O(V+E)
3.3 Dijkstra最短路径算法
算法思想:
贪心策略,每次选择当前距离起点最近的顶点进行松弛操作。
实现代码:
cpp
void dijkstra(int start) {vector<int> dist(MAXN, INT_MAX);priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;dist[start] = 0;pq.push({0, start});while (!pq.empty()) {auto [d, u] = pq.top();pq.pop();if (d > dist[u]) continue;for (auto &e : adjListW[u]) {int v = e.to;int w = e.weight;if (dist[v] > dist[u] + w) {dist[v] = dist[u] + w;pq.push({dist[v], v});}}}
}
适用条件:
-
无负权边
-
有向/无向图均可
时间复杂度:
-
普通实现:O(V²)
-
优先队列优化:O(E + VlogV)
3.4 Floyd-Warshall算法
算法思想:
动态规划,逐步允许通过更多顶点作为中转点。
实现代码:
cpp
void floyd() {int dist[MAXN][MAXN];// 初始化for (int i = 0; i < MAXN; ++i) {for (int j = 0; j < MAXN; ++j) {dist[i][j] = (i == j) ? 0 : INT_MAX;}}// 添加边// ...// 核心算法for (int k = 0; k < MAXN; ++k) {for (int i = 0; i < MAXN; ++i) {for (int j = 0; j < MAXN; ++j) {if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX) {dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);}}}}
}
特点:
-
多源最短路径
-
可处理负权边(不能有负权环)
-
代码简洁
时间复杂度:O(V³)
3.5 拓扑排序算法
算法思想:
将有向无环图(DAG)的顶点排成线性序列,使得对每条边(u,v),u在v前面。
Kahn算法实现:
cpp
vector<int> topoSort(int n) {vector<int> inDegree(n, 0);queue<int> q;vector<int> result;// 计算入度for (int u = 0; u < n; ++u) {for (int v : adjList[u]) {inDegree[v]++;}}// 入度为0的顶点入队for (int u = 0; u < n; ++u) {if (inDegree[u] == 0) {q.push(u);}}// 拓扑排序while (!q.empty()) {int u = q.front();q.pop();result.push_back(u);for (int v : adjList[u]) {if (--inDegree[v] == 0) {q.push(v);}}}return result.size() == n ? result : vector<int>();
}
应用场景:
-
任务调度
-
课程安排
-
依赖关系解析
四、图论在现实中的应用
4.1 社交网络分析
-
顶点表示用户
-
边表示好友关系
-
应用:好友推荐、社区发现
4.2 交通网络优化
-
顶点表示车站/路口
-
边表示路线
-
应用:最短路径导航
4.3 网络拓扑管理
-
顶点表示网络设备
-
边表示连接
-
应用:路由优化
4.4 知识图谱构建
-
顶点表示实体
-
边表示关系
-
应用:智能搜索
五、洛谷图论入门题目精选
5.1 P5318 【深基18.例3】查找文献
标签:图的遍历、DFS、BFS
难度:普及-
解题要点:
-
使用邻接表存储图
-
需要对邻接点排序以满足题目要求
-
分别实现DFS和BFS
5.2 P3916 图的遍历
标签:反向图、DFS
难度:普及/提高-
解题要点:
-
反向建图技巧
-
从编号大的顶点开始遍历
-
记忆化优化
5.3 P1339 [USACO09OCT]Heat Wave G
标签:最短路径、Dijkstra
难度:普及/提高-
解题要点:
-
标准Dijkstra算法实现
-
优先队列优化
-
邻接表存储
5.4 P3366 【模板】最小生成树
标签:Kruskal、Prim
难度:普及
解题要点:
-
两种最小生成树算法实现
-
并查集在Kruskal中的应用
-
优先队列在Prim中的应用
5.5 P2661 信息传递
标签:基环树、DFS
难度:提高
解题要点:
-
识别图中的最小环
-
DFS标记深度
-
时间戳技巧
六、图论学习建议
-
基础先行:先掌握图的存储和基本遍历
-
循序渐进:从简单题目开始,逐步提高难度
-
理解本质:不要死记硬背算法,理解其核心思想
-
多实践:通过大量练习培养图论思维
-
总结归纳:建立自己的解题模式库
图论作为算法竞赛和计算机科学的核心内容,需要长期的学习和积累。