news 2026/5/31 2:32:18

图论入门:从存储结构到DFS/BFS遍历,零基础也能看懂的实战教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
图论入门:从存储结构到DFS/BFS遍历,零基础也能看懂的实战教程

图论是数据结构与算法的核心模块,也是面试高频考点,但很多新手会卡在“概念抽象”“代码难写”“逻辑理不清”三个环节。本文避开复杂理论,从“用代码实现”的角度出发,手把手教你掌握图的两种核心存储结构(邻接矩阵、邻接表),再深入讲解深度优先(DFS)、广度优先(BFS)遍历算法,全程附可直接运行的C语言代码和清晰的执行逻辑,零基础也能跟着敲、跟着懂。

一、先搞懂:图是什么?为什么需要存储结构?

简单来说,图是由“顶点”和“边”组成的数据结构——比如社交网络中,每个人是“顶点”,朋友关系是“边”;地图中,城市是“顶点”,道路是“边”。

但计算机无法直接“理解”顶点和边的关系,必须通过特定的结构存储,这就有了“邻接矩阵”和“邻接表”两种经典方案:

邻接矩阵:用二维数组存,适合顶点少、边多的“稠密图”(比如地图中相邻城市);
邻接表:用“顶点数组+链表”存,适合顶点多、边少的“稀疏图”(比如社交网络)。

先从最直观的邻接矩阵开始学起,再对比理解邻接表的优势。

二、图的第一种存储结构:邻接矩阵(Adjacency Matrix)

2.1 核心逻辑:用二维数组映射顶点关系

假设图有 n 个顶点,用 n×n 的二维数组 matrix 存储:

matrix[i][j] = 权值 :表示顶点 i 和顶点 j 之间有边,权值是边的“长度”或“权重”;
matrix[i][j] = 无穷大(INF) :表示顶点 i 和顶点 j 之间无边;
matrix[i][i] = 0 :表示顶点自身到自身无边(可选)。

比如一个5顶点的图,邻接矩阵长这样( ∞ 表示无边):

plaintext

0 1 2 3 4
0 0 2 3 ∞ ∞
1 2 0 ∞ 1 ∞
2 3 ∞ 0 ∞ 4
3 ∞ 1 ∞ 0 ∞
4 ∞ ∞ 4 ∞ 0


2.2 完整代码实现(可直接运行)

新建 adjacency_matrix.c 文件,复制以下代码,包含“初始化、加边、打印”核心功能:

c

#include <stdio.h>
#include <stdbool.h>

// 配置参数:最大顶点数、无边标识(无穷大)
#define MAX_VERTICES 100
#define INF 999999

// 邻接矩阵结构体:存储顶点和边的关系
typedef struct {
char vertex[MAX_VERTICES]; // 顶点数组(用'0'/'1'/'2'...标识顶点)
int matrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵(存边权)
int vertex_num; // 实际顶点数量
} AdjMatrixGraph;

// 1. 初始化邻接矩阵
void initAdjMatrix(AdjMatrixGraph *graph, int n) {
graph->vertex_num = n;
// 初始化顶点:默认用'0'~'n-1'标识(也可自定义为字母,比如'A')
for (int i = 0; i < n; i++) {
graph->vertex[i] = '0' + i;
}
// 初始化矩阵:无边设为INF,自身设为0
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
graph->matrix[i][j] = (i == j) ? 0 : INF;
}
}
}

// 2. 添加无向边(v1、v2是顶点索引,weight是边权)
void addEdgeMatrix(AdjMatrixGraph *graph, int v1, int v2, int weight) {
// 先检查顶点索引是否合法(避免越界)
if (v1 < 0 || v1 >= graph->vertex_num || v2 < 0 || v2 >= graph->vertex_num) {
printf("顶点索引错误,添加边失败!\n");
return;
}
// 无向图:边是双向的,所以matrix[v1][v2]和matrix[v2][v1]都要设为weight
graph->matrix[v1][v2] = weight;
graph->matrix[v2][v1] = weight;
}

// 3. 打印邻接矩阵(直观查看图结构)
void printAdjMatrix(AdjMatrixGraph *graph) {
printf("邻接矩阵(INF表示无边):\n");
// 先打印顶点表头(方便对应)
printf(" ");
for (int i = 0; i < graph->vertex_num; i++) {
printf("%c ", graph->vertex[i]);
}
printf("\n");
// 打印矩阵内容
for (int i = 0; i < graph->vertex_num; i++) {
printf("%c ", graph->vertex[i]);
for (int j = 0; j < graph->vertex_num; j++) {
if (graph->matrix[i][j] == INF) {
printf("∞ ");
} else {
printf("%d ", graph->matrix[i][j]);
}
}
printf("\n");
}
}

// 主函数:测试邻接矩阵
int main() {
AdjMatrixGraph graph;
// 1. 初始化5个顶点的图
initAdjMatrix(&graph, 5);

// 2. 添加无向边(顶点0-1边权2,0-2边权3,1-3边权1,2-4边权4)
addEdgeMatrix(&graph, 0, 1, 2);
addEdgeMatrix(&graph, 0, 2, 3);
addEdgeMatrix(&graph, 1, 3, 1);
addEdgeMatrix(&graph, 2, 4, 4);

// 3. 打印结果
printAdjMatrix(&graph);
return 0;
}


2.3 编译运行步骤(新手必看)

1. 环境准备:确保已安装MinGW(C语言编译器),若未安装,可参考文末“附录”的MinGW安装教程;
2. 保存代码:用VS Code打开文件,按 Ctrl+S 保存为 adjacency_matrix.c ;
3. 编译代码:打开VS Code终端(“终端”→“新建终端”),输入命令:
bash

gcc adjacency_matrix.c -o adjacency_matrix

4. 运行代码:输入命令执行生成的可执行文件:
bash

./adjacency_matrix.exe

5. 查看结果:终端会输出5×5的邻接矩阵,与前文示例一致,说明代码运行成功。

三、图的第二种存储结构:邻接表(Adjacency List)

3.1 核心逻辑:用“顶点数组+链表”节省空间

邻接矩阵的缺点很明显:如果顶点多但边少(比如1000个顶点,只有100条边),二维数组会浪费大量空间(大部分都是 INF )。

邻接表的解决方案是:

- 顶点数组:存储所有顶点,每个顶点对应一个“链表头”;
- 链表:每个顶点的链表,只存储该顶点的“邻接点”和“边权”,无边的顶点不占空间。

比如同样是5顶点的图,邻接表长这样:

plaintext

0 -> 2(3) -> 1(2) -> NULL
1 -> 3(1) -> 0(2) -> NULL
2 -> 4(4) -> 0(3) -> NULL
3 -> 1(1) -> NULL
4 -> 2(4) -> NULL


3.2 完整代码实现(可直接运行)

新建 adjacency_list.c 文件,复制以下代码,核心是“边节点结构体+顶点结构体+邻接表操作”:

c

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_VERTICES 100 // 最大顶点数

// 1. 边节点结构体:存储邻接点和边权(链表的每个节点)
typedef struct EdgeNode {
int adjvex; // 邻接点的索引(对应顶点数组的下标)
int weight; // 边的权值
struct EdgeNode *next; // 指向下一个边节点(链表指针)
} EdgeNode;

// 2. 顶点结构体:存储顶点数据和边链表头指针
typedef struct VertexNode {
char data; // 顶点标识(如'0'/'1')
EdgeNode *firstedge; // 指向边链表的头指针(初始为NULL)
} VertexNode;

// 3. 邻接表结构体:顶点数组+顶点数量
typedef struct {
VertexNode adjlist[MAX_VERTICES]; // 顶点数组
int vertex_num; // 实际顶点数量
int edge_num; // 实际边数量(可选,用于统计)
} AdjListGraph;

// 4. 初始化邻接表
void initAdjList(AdjListGraph *graph, int n) {
graph->vertex_num = n;
graph->edge_num = 0;
// 初始化每个顶点:数据设为'0'~'n-1',边链表头指针设为NULL
for (int i = 0; i < n; i++) {
graph->adjlist[i].data = '0' + i;
graph->adjlist[i].firstedge = NULL;
}
}

// 5. 添加无向边(v1、v2是顶点索引,weight是边权)
void addEdgeList(AdjListGraph *graph, int v1, int v2, int weight) {
// 检查顶点索引合法性
if (v1 < 0 || v1 >= graph->vertex_num || v2 < 0 || v2 >= graph->vertex_num) {
printf("顶点索引错误,添加边失败!\n");
return;
}

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

JS核心语法

特性varletconst块级作用域❌&#xff08;函数级作用域&#xff09;✅✅变量提升✅&#xff08;可先使用后声明&#xff09;❌&#xff08;暂时性死区&#xff09;❌&#xff08;暂时性死区&#xff09;重复声明✅❌❌重新赋值✅✅❌&#xff08;声明时必须赋值&#xff09;// …

作者头像 李华
网站建设 2026/5/30 3:24:10

分公司组织架构图在线设计 总部分支管理模板

良功绘图网站 (https://www.lghuitu.com ) 在企业规模化发展的进程中&#xff0c;分公司的设立成为拓展市场、优化资源配置的重要举措。而总部分支之间的高效协同&#xff0c;离不开清晰、科学的组织架构作为支撑。分公司组织架构图作为直观呈现管理层级、部门设置、权责划分的…

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

KD-Tree的查询原理

好的&#xff0c;让我详细解释KD-Tree的查询原理&#xff0c;以及为什么它能将时间复杂度从O(n)降到O(log n)。 KD-Tree的基本结构 KD-Tree&#xff08;k-dimensional tree&#xff09;是一种用于多维空间的数据结构&#xff0c;特别适合范围搜索和最近邻搜索。 构建过程示例…

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

基于Mask R-CNN的道路路面损伤自动检测与分类研究

1. 基于Mask R-CNN的道路路面损伤自动检测与分类研究 1.1. 引言 随着城市化进程的加速&#xff0c;道路基础设施的维护变得越来越重要。传统的人工检测方法效率低下、成本高昂&#xff0c;且存在安全隐患。&#x1f6a7; 近年来&#xff0c;计算机视觉技术的快速发展为道路路…

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

最近在研究高速列车的主动悬挂系统,发现H无穷控制策略在这个领域挺有意思的。今天就来聊聊基于H无穷控制策略的横摆半车9自由度高速列车主动悬挂

基于H无穷控制策略的横摆半车9自由度高速列车主动悬挂首先&#xff0c;我们得明白什么是H无穷控制。简单来说&#xff0c;H无穷控制是一种鲁棒控制方法&#xff0c;能够在系统存在不确定性和外部干扰的情况下&#xff0c;保证系统的稳定性和性能。对于高速列车这种复杂系统&…

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

Ubuntu硬盘空间不够?一文带你理清过程的根分区无损扩容实战指南

复杂分区布局下的 Ubuntu 根目录无损扩容实践&#xff1a;从引导参数调试到扇区移位 摘要 本文详细记录了在一块 1TB NVMe 固态硬盘&#xff08;WD_BLACK SN770&#xff09;上&#xff0c;解决 Ubuntu 根分区&#xff08;/&#xff09;空间不足问题的全过程。本次扩容的特殊性在…

作者头像 李华