C语言图论:无向图基础

本文献给:

准备学习图论的C语言学习者。如果你想要理解图的基本概念和算法------本文将为你带来无向图的基础概念及简单算法。

你将学到:

  1. 无向图的基本概念和核心术语
  2. 邻接矩阵和邻接表两种存储方式
  3. 深度优先搜索和广度优先搜索遍历
  4. 连通分量、最短路径和最小生成树概念

目录

第一部分:无向图基础概念

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;
            }
        }
    }
}

第五部分:总结

核心要点回顾

  1. 无向图定义 : G = ( V , E ) G = (V, E) G=(V,E), E E E 是无序顶点对集合
  2. 存储结构
    • 邻接矩阵: O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2) 空间,适合稠密图
    • 邻接表: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣) 空间,适合稀疏图
  3. 遍历算法
    • DFS: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣),适合路径查找
    • BFS: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣),适合最短路径(无权图)
  4. 基础算法
    • 连通分量: 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语言 #图论 #无向图 #算法 #数据结构

相关推荐
秋深枫叶红1 小时前
嵌入式第二十九篇——数据结构——树
数据结构·学习·算法·深度优先
渡我白衣1 小时前
计算机组成原理(3):计算机软件
java·c语言·开发语言·jvm·c++·人工智能·python
小龙报1 小时前
【C语言初阶】动态内存分配实战指南:C 语言 4 大函数使用 + 经典笔试题 + 柔性数组优势与内存区域
android·c语言·开发语言·数据结构·c++·算法·visual studio
小龙报1 小时前
【算法通关指南:算法基础篇(三)】一维差分专题:1.【模板】差分 2.海底高铁
android·c语言·数据结构·c++·算法·leetcode·visual studio
不悔哥1 小时前
路由器特性——网络状态检测
linux·c语言·网络·tcp/ip·智能路由器
小李小李快乐不已2 小时前
图论理论基础(5)
数据结构·c++·算法·机器学习·动态规划·图论
承渊政道2 小时前
C++学习之旅【C++基础知识介绍】
c语言·c++·学习·程序人生
烛衔溟2 小时前
C语言图论:有向图基础
c语言·数据结构·图论·有向图
枫叶丹42 小时前
【Qt开发】Qt窗口(七) -> QColorDialog 颜色对话框
c语言·开发语言·c++·qt