什么是图:把世界抽象成点和线
地图上城市之间有公路相连;社交网络里你和朋友互相关注;网页之间用超链接跳转。这些看似不同的场景,底层都是同一个数学结构——图(Graph)。
图由两部分组成:顶点(Vertex)表示城市/人/网页,边(Edge)表示公路/好友关系/超链接。边可以有方向(你关注他,他不一定关注你),也可以有权重(公路长度、好友亲密度)。
图论就是研究这种"点和线"结构的数学分支。竞赛里大量题目本质上就是在图上做搜索、找路径、算连通性。
把实际问题抽象成图,问题就解决了一半。
- 图顶点 + 边的组合。
- 邻接表用 vector 数组存图。
- DFS深度优先,一条路走到底再回头。
- BFS广度优先,一圈圈扩散求最短路。
怎么存图:邻接表与邻接矩阵
电脑怎么存一张图?两种方式:
邻接矩阵:用一个二维数组 g[i][j] 表示 i 和 j 之间有没有边。优点是判断两点间有没有边只要 O(1);缺点是 n 个点要开 n 的平方个空间,n=10000 时内存不够。
邻接表:用一个 vector 数组,g[i] 存着 i 所有邻居的列表。n=10000 只有边数个空间,竞赛里最常用。
#include <vector>
using namespace std;
vector<int> g[100005]; // 邻接表
void addEdge(int u, int v) {
g[u].push_back(v);
g[v].push_back(u); // 无向图要加两次
}
入门阶段先掌握邻接表,邻接矩阵只在 n 很小(n≤500)时用。
DFS 深度优先:一条路走到黑
DFS(Depth-First Search)的思路:从起点出发,选一个邻居走过去,再选那个点的一个邻居继续走,直到走不动了就回头换路。就像走迷宫,沿着一条路一直走到死胡同,再退回来走另一条。
bool visited[100005];
void dfs(int u) {
visited[u] = true;
cout << u << " ";
for (int v : g[u]) {
if (!visited[v]) {
dfs(v);
}
}
}
// 调用: dfs(1);
关键是 visited 数组,标记哪些点已经走过,防止无限循环。DFS 适合判断连通性(两个点在不在一起)、判断有没有环。
BFS 广度优先:一圈一圈扩散
BFS(Breadth-First Search)的思路:从起点开始,先访问所有距离 1 的点,再访问距离 2 的,再访问距离 3 的……一圈一圈向外扩散。就像往水里扔石头,波纹一圈圈扩大。
#include <queue>
int dist[100005];
bool visited[100005];
void bfs(int start) {
queue<int> q;
q.push(start);
visited[start] = true;
dist[start] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : g[u]) {
if (!visited[v]) {
visited[v] = true;
dist[v] = dist[u] + 1;
q.push(v);
}
}
}
}
BFS 用队列实现。它的核心优势是:在无权图上,BFS 第一次访问到某个点时,走的就是最短路径。
最短路:BFS 求无权图最短步数
"无权图"就是每条边长度都是 1。这种图上,从起点到终点最少要走几步?BFS 天然解决这个问题——dist[v] 存的就是起点到 v 的最短步数。
举个例子:迷宫里从左上角走到右下角,每步可以上下左右走一格,最少几步?把每个格子看成一个点,相邻格子之间连边,跑一次 BFS,dist[终点] 就是答案。
如果边有权重(公路长度不同),BFS 就不够用了,要用 Dijkstra 算法。那是图论进阶内容,入门阶段先把无权图 BFS 练熟。
- 邻接表vector 数组存图。
- visited防止重复访问。
- queueBFS 的核心容器。
- dist最短步数。
实战:迷宫最短路径问题
题目:给定一个 n×m 的迷宫,0 表示空地,1 表示墙。从左上角 (0,0) 走到右下角 (n-1,m-1),每次只能上下左右走一格,最少几步?
#include <bits/stdc++.h>
using namespace std;
int n, m;
int maze[105][105];
int dist[105][105];
int dx[] = {1, -1, 0, 0};
int dy[] = {0, 0, 1, -1};
int bfs() {
queue<pair<int,int>> q;
q.push({0, 0});
dist[0][0] = 0;
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
for (int d = 0; d < 4; d++) {
int nx = x + dx[d], ny = y + dy[d];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (maze[nx][ny] == 1) continue;
if (dist[nx][ny] != -1) continue;
dist[nx][ny] = dist[x][y] + 1;
q.push({nx, ny});
}
}
return dist[n-1][m-1];
}
这是图论入门最经典的题型,几乎每场信息学竞赛都会出现。把二维迷宫当成图,用 BFS 跑一遍,答案就出来了。
图论之后:竞赛里的下一阶段
学完本文,孩子已经掌握了图论最基础的两块:存图和搜索。后续可以按这个路线深入:
- 最短路进阶:Dijkstra 算法处理带权图,Floyd 多源最短路。
- 最小生成树:Kruskal 和 Prim,把所有点连起来用最少的边。
- 拓扑排序:处理有先后顺序的任务安排。
- 并查集:快速判断两个点在不在一起。
这些都是 CSP-J/S 竞赛的高频考点。建议每个专题配 5 到 10 道真题练手,光看不写代码是学不会图论的。
图论是信息学奥赛半壁江山。学会了,竞赛就过了一大关。