本文献给:
准备学习图论的C语言学习者。如果你想要理解图的基本概念和算法------本文将为你带来无向图的基础概念及简单算法。
你将学到:
- 无向图的基本概念和核心术语
- 邻接矩阵和邻接表两种存储方式
- 深度优先搜索和广度优先搜索遍历
- 连通分量、最短路径和最小生成树概念
目录
- 第一部分:无向图基础概念
-
- [1. 什么是无向图?](#1. 什么是无向图?)
- [2. 基本术语](#2. 基本术语)
- 第二部分:存储方式
-
- [1. 邻接矩阵](#1. 邻接矩阵)
- [2. 邻接表](#2. 邻接表)
- [3. 存储方式对比](#3. 存储方式对比)
- 第三部分:图的遍历
-
- [1. 深度优先搜索(DFS)](#1. 深度优先搜索(DFS))
- [2. 广度优先搜索(BFS)](#2. 广度优先搜索(BFS))
- 第四部分:基本算法
-
- [1. 连通分量](#1. 连通分量)
- [2. 无权图最短路径](#2. 无权图最短路径)
- [3. 最小生成树(Prim算法)](#3. 最小生成树(Prim算法))
- 第五部分:总结
第一部分:无向图基础概念
1. 什么是无向图?
无向图 G = ( V , E ) G = (V, E) G=(V,E) 由顶点集合 V V V 和边集合 E E E 组成,其中每条边 e ∈ E e \in E e∈E 是无序的顶点对 ( u , v ) (u, v) (u,v),表示 u u u 和 v v v 之间的双向连接。
数学定义:
G = ( V , E ) 其中 E ⊆ { { u , v } ∣ u , v ∈ V , u ≠ v } G = (V, E) \quad \text{其中} \quad E \subseteq \{ \{u, v\} \mid u, v \in V, u \neq v \} G=(V,E)其中E⊆{{u,v}∣u,v∈V,u=v}
示例:
顶点集合:V = {A, B, C, D}
边集合:E = {(A,B), (A,C), (B,C), (C,D)}
2. 基本术语
- 顶点(Vertex) :图的基本元素, v ∈ V v \in V v∈V
- 边(Edge) :连接两个顶点的线, e = ( u , v ) ∈ E e = (u, v) \in E e=(u,v)∈E
- 度数(Degree) : deg ( v ) = ∣ { u ∈ V ∣ ( v , u ) ∈ E } ∣ \deg(v) = |\{ u \in V \mid (v, u) \in E \}| deg(v)=∣{u∈V∣(v,u)∈E}∣
- 握手定理 : ∑ v ∈ V deg ( v ) = 2 ∣ E ∣ \sum_{v \in V} \deg(v) = 2|E| ∑v∈Vdeg(v)=2∣E∣
- 路径(Path) :顶点序列 v 0 , v 1 , . . . , v k v_0, v_1, ..., v_k v0,v1,...,vk,其中 ( v i , v i + 1 ) ∈ E (v_i, v_{i+1}) \in E (vi,vi+1)∈E
- 连通图 : ∀ u , v ∈ V \forall u, v \in V ∀u,v∈V,存在从 u u u 到 v v v 的路径
第二部分:存储方式
1. 邻接矩阵
使用 n × n n \times n n×n 矩阵 A A A 存储,其中 n = ∣ V ∣ n = |V| n=∣V∣:
A [ i ] [ j ] = { 1 if ( i , j ) ∈ E 0 otherwise A[i][j] = \begin{cases} 1 & \text{if } (i,j) \in E \\ 0 & \text{otherwise} \end{cases} A[i][j]={10if (i,j)∈Eotherwise
c
#define MAX_V 100
typedef struct {
int matrix[MAX_V][MAX_V]; // 邻接矩阵
int vertex_count; // |V|
int edge_count; // |E|
} GraphMatrix;
// 初始化:时间复杂度 O(n²),空间复杂度 O(n²)
void init_graph(GraphMatrix* g, int n) {
g->vertex_count = n;
g->edge_count = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
g->matrix[i][j] = 0;
}
}
}
// 添加边:时间复杂度 O(1)
void add_edge(GraphMatrix* g, int u, int v) {
if (u >= g->vertex_count || v >= g->vertex_count) return;
// 无向图,矩阵对称
g->matrix[u][v] = 1;
g->matrix[v][u] = 1;
g->edge_count++;
}
2. 邻接表
对于每个顶点 v v v,维护其邻居列表 A d j [ v ] = { u ∈ V ∣ ( v , u ) ∈ E } Adj[v] = \{ u \in V \mid (v, u) \in E \} Adj[v]={u∈V∣(v,u)∈E}
c
typedef struct Node {
int vertex;
struct Node* next;
} Node;
typedef struct {
Node* lists[MAX_V]; // 邻接表数组
int vertex_count; // |V|
int edge_count; // |E|
} GraphList;
// 添加边:时间复杂度 O(1),但需要更新两个列表
void add_edge_list(GraphList* g, int u, int v) {
// 添加到u的邻居列表
Node* node_u = create_node(v);
node_u->next = g->lists[u];
g->lists[u] = node_u;
// 添加到v的邻居列表
Node* node_v = create_node(u);
node_v->next = g->lists[v];
g->lists[v] = node_v;
g->edge_count++;
}
3. 存储方式对比
| 特性 | 邻接矩阵 | 邻接表 | 时间复杂度 |
|---|---|---|---|
| 空间 | $O( | V | ^2)$ |
| 检查边 ( u , v ) (u,v) (u,v) | O ( 1 ) O(1) O(1) | O ( deg ( u ) ) O(\deg(u)) O(deg(u)) | 邻接矩阵更快 |
| 遍历 v v v 的邻居 | $O( | V | )$ |
| 添加边 | O ( 1 ) O(1) O(1) | O ( 1 ) O(1) O(1) | 相当 |
| 删除边 | O ( 1 ) O(1) O(1) | O ( deg ( v ) ) O(\deg(v)) O(deg(v)) | 邻接矩阵更快 |
选择建议:
- 稠密图( ∣ E ∣ ≈ ∣ V ∣ 2 |E| \approx |V|^2 ∣E∣≈∣V∣2):邻接矩阵
- 稀疏图( ∣ E ∣ ≪ ∣ V ∣ 2 |E| \ll |V|^2 ∣E∣≪∣V∣2):邻接表
第三部分:图的遍历
1. 深度优先搜索(DFS)
沿着路径深入到底再回溯,使用递归或栈实现。
算法复杂度:
- 邻接矩阵: O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)
- 邻接表: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣)
c
// DFS递归实现
void dfs_matrix(GraphMatrix* g, int v, int visited[]) {
visited[v] = 1; // 标记访问
for (int i = 0; i < g->vertex_count; i++) {
if (g->matrix[v][i] && !visited[i]) {
dfs_matrix(g, i, visited);
}
}
}
// DFS栈实现(避免递归深度限制)
void dfs_stack(GraphMatrix* g, int start) {
int visited[MAX_V] = {0};
int stack[MAX_V];
int top = -1;
stack[++top] = start;
while (top >= 0) {
int v = stack[top--];
if (!visited[v]) {
visited[v] = 1;
// 逆序压入栈,保持与递归相同顺序
for (int i = g->vertex_count - 1; i >= 0; i--) {
if (g->matrix[v][i] && !visited[i]) {
stack[++top] = i;
}
}
}
}
}
2. 广度优先搜索(BFS)
逐层扩展,先访问离起点最近的顶点,使用队列实现。
算法复杂度:
- 邻接矩阵: O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)
- 邻接表: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣)
c
// BFS队列实现
void bfs_matrix(GraphMatrix* g, int start) {
int visited[MAX_V] = {0};
int queue[MAX_V];
int front = 0, rear = 0;
visited[start] = 1;
queue[rear++] = start;
while (front < rear) {
int v = queue[front++];
for (int i = 0; i < g->vertex_count; i++) {
if (g->matrix[v][i] && !visited[i]) {
visited[i] = 1;
queue[rear++] = i;
}
}
}
}
第四部分:基本算法
1. 连通分量
连通分量是极大的连通子图。使用DFS或BFS查找所有连通分量。
算法复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣)
c
// 查找所有连通分量
void find_components(GraphMatrix* g) {
int visited[MAX_V] = {0};
int component = 0;
for (int i = 0; i < g->vertex_count; i++) {
if (!visited[i]) {
component++;
// 使用DFS遍历该连通分量
dfs_matrix(g, i, visited);
}
}
}
2. 无权图最短路径
使用BFS计算从起点到所有顶点的最短距离。
算法复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣)
c
// 计算最短路径(无权图)
void shortest_path(GraphMatrix* g, int start, int distance[]) {
for (int i = 0; i < g->vertex_count; i++) {
distance[i] = -1; // -1表示不可达
}
int visited[MAX_V] = {0};
int queue[MAX_V];
int front = 0, rear = 0;
visited[start] = 1;
distance[start] = 0;
queue[rear++] = start;
while (front < rear) {
int v = queue[front++];
for (int i = 0; i < g->vertex_count; i++) {
if (g->matrix[v][i] && !visited[i]) {
visited[i] = 1;
distance[i] = distance[v] + 1;
queue[rear++] = i;
}
}
}
}
3. 最小生成树(Prim算法)
从单个顶点开始,逐步扩展生成树,每次选择连接树和非树顶点的最小权重边。
算法复杂度:
- 朴素实现: O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)
- 优先队列优化: O ( ∣ E ∣ log ∣ V ∣ ) O(|E| \log |V|) O(∣E∣log∣V∣)
c
// Prim算法朴素实现
void prim_mst(GraphMatrix* g, int parent[]) {
int in_mst[MAX_V] = {0}; // 标记顶点是否在MST中
int key[MAX_V]; // 顶点到MST的最小边权重
// 初始化
for (int i = 0; i < g->vertex_count; i++) {
key[i] = INT_MAX;
parent[i] = -1;
}
key[0] = 0; // 从顶点0开始
parent[0] = -1;
// 每次添加一个顶点到MST
for (int count = 0; count < g->vertex_count - 1; count++) {
// 找到不在MST中且key最小的顶点
int u = -1;
int min_key = INT_MAX;
for (int v = 0; v < g->vertex_count; v++) {
if (!in_mst[v] && key[v] < min_key) {
min_key = key[v];
u = v;
}
}
if (u == -1) break; // 图不连通
in_mst[u] = 1;
// 更新邻居的key值
for (int v = 0; v < g->vertex_count; v++) {
if (g->matrix[u][v] && !in_mst[v] && g->matrix[u][v] < key[v]) {
key[v] = g->matrix[u][v];
parent[v] = u;
}
}
}
}
第五部分:总结
核心要点回顾
- 无向图定义 : G = ( V , E ) G = (V, E) G=(V,E), E E E 是无序顶点对集合
- 存储结构 :
- 邻接矩阵: O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2) 空间,适合稠密图
- 邻接表: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣) 空间,适合稀疏图
- 遍历算法 :
- DFS: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣),适合路径查找
- BFS: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣),适合最短路径(无权图)
- 基础算法 :
- 连通分量: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣)
- 最短路径(无权图): O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣)
- 最小生成树(Prim): O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2) 或 O ( ∣ E ∣ log ∣ V ∣ ) O(|E| \log |V|) O(∣E∣log∣V∣)
觉得文章有帮助?别忘了:
👍 点赞 👍 - 给我一点鼓励
⭐ 收藏 ⭐ - 方便以后查看
🔔 关注 🔔 - 获取更新通知
标签: #C语言 #图论 #无向图 #算法 #数据结构