DFS与BFS:图遍历的两大核心算法

发布时间:2026/9/14 19:50:22

DFS与BFS:图遍历的两大核心算法 1. 深度优先搜索DFS1.1 DFS 的原理深度优先搜索的核心思想是“一条路走到黑”。从起始顶点出发沿着一条路径一直往下走直到无法继续前进时再回退到上一个分岔口选择另一条路径继续探索。这个过程很像走迷宫时先沿着一条路走到底遇到死胡同再回头换一条路。这种“走到底再回头”的策略决定了 DFS 会优先深入图的深处而不是先访问同一层的其他顶点。因此DFS 天然适合用来解决连通性判断、路径查找、拓扑排序等问题。1.2 DFS 的递归实现递归是实现 DFS 最自然的方式。我们定义一个递归函数每次访问一个顶点时先标记它为已访问然后依次对它的所有未访问的邻居顶点递归调用自身。下面是用 C 语言实现的递归 DFS 代码#include stdio.h #include stdbool.h #define MAX_VERTICES 100 // 邻接矩阵 int graph[MAX_VERTICES][MAX_VERTICES]; bool visited[MAX_VERTICES]; int vertexCount; // 递归深度优先搜索 void dfsRecursive(int vertex) { visited[vertex] true; printf(访问顶点: %d\n, vertex); for (int i 0; i vertexCount; i) { if (graph[vertex][i] 1 !visited[i]) { dfsRecursive(i); } } } int main() { // 初始化图示例5 个顶点 vertexCount 5; // 这里省略图的初始化赋值代码 // 从顶点 0 开始遍历 dfsRecursive(0); return 0; }递归实现代码简洁逻辑清晰非常适合初学者理解 DFS 的思想。但递归调用会占用系统栈空间当图的规模很大时可能会导致栈溢出。1.3 DFS 的非递归实现非递归实现使用显式的栈来模拟递归过程。我们先把起始顶点压入栈然后循环执行弹出栈顶顶点如果它未被访问则标记并访问再将其所有未访问的邻居压入栈。下面是 C 语言的非递归实现#include stdio.h #include stdbool.h #define MAX_VERTICES 100 #define STACK_SIZE 100 int graph[MAX_VERTICES][MAX_VERTICES]; bool visited[MAX_VERTICES]; int vertexCount; int stack[STACK_SIZE]; int top -1; void push(int value) { if (top STACK_SIZE - 1) { stack[top] value; } } int pop() { if (top 0) { return stack[top--]; } return -1; } bool isEmpty() { return top -1; } // 非递归深度优先搜索 void dfsIterative(int startVertex) { push(startVertex); while (!isEmpty()) { int vertex pop(); if (!visited[vertex]) { visited[vertex] true; printf(访问顶点: %d\n, vertex); // 将未访问的邻居压入栈 for (int i vertexCount - 1; i 0; i--) { if (graph[vertex][i] 1 !visited[i]) { push(i); } } } } } int main() { vertexCount 5; // 这里省略图的初始化赋值代码 dfsIterative(0); return 0; }非递归实现避免了递归调用带来的栈溢出风险但代码相对复杂一些。两种实现方式访问顶点的顺序可能略有不同但都能正确完成遍历。1.4 DFS 的时间复杂度DFS 的时间复杂度取决于图的存储方式。如果使用邻接矩阵存储遍历每个顶点的所有邻居需要检查一整行因此时间复杂度为 O(V²)其中 V 是顶点数。如果使用邻接表存储每条边只会被检查一次时间复杂度为 O(V E)其中 E 是边数。空间复杂度方面递归实现需要 O(V) 的递归栈空间非递归实现需要 O(V) 的显式栈空间。2. 广度优先搜索BFS2.1 BFS 的原理广度优先搜索的核心思想是“层层推进”。从起始顶点出发先访问所有与它直接相连的邻居顶点然后再依次访问这些邻居的邻居就像水波一样一圈一圈向外扩散。这种策略保证了 BFS 总是先访问距离起始顶点最近的顶点。由于 BFS 按层次推进的特性它非常适合用来求解最短路径问题尤其是在无权图中BFS 找到的路径一定是最短路径。2.2 BFS 的队列实现BFS 使用队列来管理待访问的顶点。队列的特点是先进先出这正好符合 BFS 逐层访问的需求。算法流程如下先将起始顶点入队并标记为已访问然后循环执行从队首取出一个顶点并访问再将其所有未访问的邻居入队并标记。下面是 C 语言的 BFS 实现#include stdio.h #include stdbool.h #define MAX_VERTICES 100 #define QUEUE_SIZE 100 int graph[MAX_VERTICES][MAX_VERTICES]; bool visited[MAX_VERTICES]; int vertexCount; int queue[QUEUE_SIZE]; int front 0; int rear 0; void enqueue(int value) { if (rear QUEUE_SIZE) { queue[rear] value; } } int dequeue() { if (front rear) { return queue[front]; } return -1; } bool isQueueEmpty() { return front rear; } // 广度优先搜索 void bfs(int startVertex) { enqueue(startVertex); visited[startVertex] true; while (!isQueueEmpty()) { int vertex dequeue(); printf(访问顶点: %d\n, vertex); for (int i 0; i vertexCount; i) { if (graph[vertex][i] 1 !visited[i]) { enqueue(i); visited[i] true; } } } } int main() { vertexCount 5; // 这里省略图的初始化赋值代码 bfs(0); return 0; }注意在 BFS 中顶点在入队时就要标记为已访问而不是在出队时标记。这样可以避免同一个顶点被重复加入队列保证算法的正确性。2.3 BFS 的时间复杂度BFS 的时间复杂度与 DFS 相同。使用邻接矩阵时时间复杂度为 O(V²)使用邻接表时时间复杂度为 O(V E)。空间复杂度方面BFS 需要 O(V) 的队列空间来存储待访问的顶点。3. DFS 与 BFS 的对比DFS 和 BFS 各有特点适用于不同的场景。下面从几个维度进行对比对比维度深度优先搜索DFS广度优先搜索BFS核心思想一条路走到底再回头层层推进逐层扩散数据结构栈递归或显式栈队列时间复杂度O(V²) 或 O(VE)O(V²) 或 O(VE)空间复杂度O(V)O(V)最短路径不保证最短无权图中保证最短典型应用连通性判断、拓扑排序、回溯搜索最短路径、层次遍历、社交网络好友推荐简单来说如果你需要找到一条路径或者判断图是否连通DFS 是不错的选择如果你需要找到最短路径或者按层次处理顶点BFS 更合适。4. 代码示例下面给出一个完整的 C 语言程序演示如何在实际项目中应用 DFS 和 BFS 遍历一个无向图。程序首先构建一个包含 6 个顶点的图然后分别用两种算法进行遍历#include stdio.h #include stdbool.h #define MAX_VERTICES 100 // 图结构 typedef struct { int matrix[MAX_VERTICES][MAX_VERTICES]; int vertexCount; } Graph; // 初始化图 void initGraph(Graph *g, int count) { g-vertexCount count; for (int i 0; i count; i) { for (int j 0; j count; j) { g-matrix[i][j] 0; } } } // 添加无向边 void addEdge(Graph *g, int u, int v) { g-matrix[u][v] 1; g-matrix[v][u] 1; } // 深度优先搜索递归 void dfs(Graph *g, int vertex, bool visited[]) { visited[vertex] true; printf(%d , vertex); for (int i 0; i g-vertexCount; i) { if (g-matrix[vertex][i] 1 !visited[i]) { dfs(g, i, visited); } } } // 广度优先搜索队列 void bfs(Graph *g, int startVertex) { bool visited[MAX_VERTICES] {false}; int queue[MAX_VERTICES]; int front 0, rear 0; visited[startVertex] true; queue[rear] startVertex; while (front rear) { int vertex queue[front]; printf(%d , vertex); for (int i 0; i g-vertexCount; i) { if (g-matrix[vertex][i] 1 !visited[i]) { visited[i] true; queue[rear] i; } } } } int main() { Graph g; initGraph(g, 6); // 构建图0-1, 0-2, 1-3, 1-4, 2-4, 3-5, 4-5 addEdge(g, 0, 1); addEdge(g, 0, 2); addEdge(g, 1, 3); addEdge(g, 1, 4); addEdge(g, 2, 4); addEdge(g, 3, 5); addEdge(g, 4, 5); bool visited[MAX_VERTICES] {false}; printf(深度优先搜索DFS遍历结果: ); dfs(g, 0, visited); printf(\n); printf(广度优先搜索BFS遍历结果: ); bfs(g, 0); printf(\n); return 0; }运行这个程序你会看到 DFS 和 BFS 以不同的顺序访问图中的顶点。DFS 会沿着一条路径深入到底而 BFS 会按层次逐层展开。你可以尝试修改图的连接关系观察两种算法的遍历顺序如何变化从而加深对它们的理解。5. 总结图的遍历是图算法的基础DFS 和 BFS 是两种最核心的遍历策略。DFS 借助栈实现“深入优先”适合解决连通性、路径搜索等问题BFS 借助队列实现“广度优先”适合解决最短路径、层次遍历等问题。两者的时间复杂度相同选择哪种算法主要取决于具体问题的需求。对于初学者来说建议先理解两种算法的核心思想再动手实现代码最后通过实际例子观察它们的遍历顺序差异。掌握了 DFS 和 BFS你就为学习更复杂的图算法如最短路径、最小生成树等打下了坚实的基础。
延伸阅读

更多相关文章

2026/9/14 19:45:22

企业数字化转型的挑战与破局之道

1. 数字化转型的现状与挑战 过去十年间,数字化转型已经从企业的可选项变成了必选项。根据麦肯锡最新研究显示,85%的企业已经启动数字化项目,但仅有30%能实现预期效益。这种落差背后,是大多数组织在转型过程中遇到的系统性障碍。 …

2026/9/14 19:45:22

西安在职提升学历:2026 年成考和自考到底怎么选

直接答案:在职的人选路径,第一变量不是"哪个含金量高",而是你每周能稳定拿出多少时间。能空出一次统考、希望节奏规整,看成考;时间碎但自律强、想按自己节奏推进,看自考;完全无法保证…

2026/9/14 20:00:23

技术项目命名指南:从无标题到好标题的实践

1. 项目概述作为一名从业多年的技术博主,我经常遇到这样的情况:手头有个不错的项目想法,却苦于找不到合适的标题来概括。这种情况在技术分享领域尤为常见——我们可能花了几周时间完成一个精彩的项目,却在最后一步"取名"…

2026/9/14 20:00:23

MATLAB图像处理在秸秆覆盖率估算中的应用

1. 秸秆覆盖率估算研究的背景与意义秸秆作为农业生产的重要副产品,其覆盖状况直接影响土壤质量、水分保持和作物生长。传统的人工测量方法存在效率低、主观性强等问题,而基于MATLAB的图像处理方法为解决这一难题提供了新的技术路径。在农业遥感领域&…

2026/9/14 20:00:23

COMSOL多物理场耦合在光伏集热器建模中的应用

1. 光伏集热器建模的独特挑战光伏集热器(PV-T)这个玩意儿确实挺有意思的,它把光伏发电和太阳能集热两个功能集成在一起,听起来很美好,但建模的时候简直就是个"混世魔王"。我去年给一家新能源企业做咨询时就遇…

2026/9/14 20:00:23

C++编译流程详解:从预处理到链接的完整指南

1. C编译方法概述:从源码到可执行文件的旅程刚接触C的新手往往会被这样的场景困扰:在终端输入g main.cpp后,一个可执行文件就神奇地出现了。但当你需要调试复杂项目时,这种"一步到位"的编译方式反而会成为效率杀手。实际…

2026/9/14 19:55:22

企业微信多账号接口实战:实例隔离与统一网关

「企业微信多账号接口」要解决的是:多个企微号同时运营多批外部群,数据不串、权限不混、掉线互不影响。 这篇讲接口层怎么做。 多账号模型 每个企微号一个 instance_id。所有登录、发送、回执、日志必须带它。账号绑定用途:推送号、接待号、…

2026/9/14 2:17:50

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/14 0:03:22

KCF目标跟踪算法与OTB工程实现:毕业设计实战解析

简介:这是一份基于KCF核相关滤波算法、融合尺度池与抗遮挡处理的目标检测跟踪MATLAB完整源码,主要面向计算机相关专业准备毕业设计、课程设计或期末大作业的学生,也适合需要项目实战练习的初学者。源码在OTB数据集上完成验证,能够…

2026/9/14 0:03:22

语音情感识别实战:Keras实现LSTM、CNN、SVM与MLP多模型对比

简介:面向语音情感识别入门与进阶开发者,这份基于Keras的项目源码完整实现了LSTM、CNN、SVM、MLP四种模型,兼容Python3.8与Keras/TensorFlow2环境。压缩包内含49个文件,大小约70.31MB,主体包括Python脚本、yaml/json配…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/14 13:53:59

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/14 11:22:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码