首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >图的DFS与BFS遍历机制详解:存储结构到算法实现

图的DFS与BFS遍历机制详解:存储结构到算法实现

作者头像
程序员古德
发布2026-07-22 17:39:45
发布2026-07-22 17:39:45
590
举报

说人话、重实战、讲干货 我是程序员古德,你的专属软考顾问 本篇是我更新的第 463 篇软考原创文章,同时还提供软考报名咨询、备考规划、论文批阅、学习指导、应试答疑等多种服务。 只要是我亲身验证过的方法、踩过的备考坑,一定坦诚相告,有需求的小伙伴可以后台私信我。

图的DFS与BFS遍历机制详解:存储结构到算法实现

图的遍历是图论中最基础的操作之一,也是软件设计师考试中每年必考的核心知识点。无论是判断图的连通性、求生成树、拓扑排序,还是更复杂的最短路径和关键路径问题,都建立在深度优先搜索与广度优先搜索这两大遍历策略之上。掌握这两种遍历算法不仅意味着能写出正确的遍历序列,更要求理解其背后的数据结构支撑——邻接矩阵和邻接表的选择如何影响算法的时间效率与空间开销,以及在递归与迭代两种实现路径中如何避免常见陷阱。本文从图的存储结构出发,逐层深入到DFS的递归与栈实现、BFS的队列驱动机制,并结合历年软考真题剖析命题人的挖坑套路与解题核心思路。

一、图的形式化定义与基本术语

在进入算法细节之前,必须先建立图的精确概念模型。图是比线性表和树更为复杂的非线性数据结构,它的形式化定义是一个二元组,包含顶点集合和边集合两个要素。设图记作G等于V和E组成的有序对,其中V是非空的顶点有限集合,E是连接这些顶点的边的有限集合。根据边是否具有方向性,图可以划分为无向图和有向图两大类。无向图中的边用无序偶对表示,意味着从顶点A到顶点B的访问与从B到A的访问在逻辑上等价;有向图中的边则用有序偶对表示,表示一条从起点指向终点的有向弧,方向约束严格不可逆。

除了方向性之外,边的权重也是图的另一个重要属性。带权图也称为网络,每条边上附加一个数值表示代价、距离或容量。在软考命题中,带权图通常与最小生成树、最短路径等经典问题绑定出现。与顶点相关的术语同样不容忽视:度是依附于某顶点的边的条数,在有向图中进一步分化为出度和入度;路径定义为顶点序列中相邻顶点之间均有边相连的序列,简单路径要求路径上的顶点不重复出现;回路或环是首尾顶点相同的路径;连通性则描述图中任意两个顶点之间是否存在通路,连通分量是极大连通子图的个数。此外,生成树是包含图中所有顶点且边数最少的连通子图,生成森林则是非连通图中每个连通分量各取一棵生成树构成的集合,这些概念是理解DFS和BFS衍生应用如最小生成树和拓扑排序的基础前提。

理解这些术语的形式化含义对于正确解题至关重要。许多考生在处理遍历问题时出错,根源往往在于对连通和完全图等概念的理解停留在感性层面,而未能从定义出发严谨推演。例如,含有N个顶点的无向完全图的边数是N乘以N减一的乘积除以二,有向完全图则是N乘以N减一,这些公式不是凭空记忆的产物,而是顶点之间两两相连的排列组合结论。软考选择题中经常要求考生从给定的顶点度和边数反推图的类型,此时对基本定义和计数公式的熟练掌握就直接决定了答题的速度和准确率。考生还可以借助握手定理来快速验证:无向图中所有顶点的度之和等于边数的两倍,这一性质在检查题目条件是否合理时极为有效。

二、图的存储结构:时间与空间的博弈

将图的拓扑结构映射到计算机内存中,主要有两种经典方案:邻接矩阵和邻接表。这两种存储方式在空间复杂度、边查询效率和遍历性能上各有优劣,选择哪一种存储结构会直接影响后续DFS与BFS算法的时间复杂度。在软考真题中,判断某种存储方式适合稠密图还是稀疏图是高频考点,考生必须理解两种结构底层的工作原理而非死记结论。

邻接矩阵的数组映射与效率边界

邻接矩阵用一个N行N列的二维数组存储N个顶点的图,数组元素A的第i行第j列取值表示顶点i到顶点j之间是否存在边。对于无向图,矩阵沿主对角线对称,只需存储上三角或下三角即可节约一半空间;对于带权图,矩阵元素可以直接存储边的权值,不存在的边用无穷大或特殊标记填充。邻接矩阵的突出优势在于边查询操作的时间复杂度为常数级别,判断任意两个顶点之间是否相邻只需一次数组下标访问即可完成。此外,计算顶点的度同样高效——无向图中遍历对应行统计非零元素的个数,有向图中行和对应出度而列和对应入度。

然而邻接矩阵的空间效率存在明显的适用边界。无论图中有多少条边,矩阵都需要固定占用N的平方个存储单元。对于边数远小于完全图中的顶点数的稀疏图——例如社交网络中的好友关系,数十万用户之间真正的连接往往只有百万量级——邻接矩阵会浪费大量存储空间,导致遍历时空开销严重膨胀。软考命题中经常结合图的稠密与稀疏特性,要求考生判断在给定场景下应选择邻接矩阵还是邻接表,此时必须同时考虑图的规模和边密度两个维度。一个实用的判断标准是:当边的数量接近或超过顶点数平方的一半时,邻接矩阵在时间和空间上均具备优势;反之邻接表更为经济。

邻接表的链式组织与空间优势

邻接表为每个顶点维护一个线性链表或动态数组,表中存储该顶点的所有邻接顶点。对于有向图,每个顶点的邻接表只存储由该顶点出发的有向边的终点,因此空间复杂度与图中的边数呈线性关系,远优于邻接矩阵在处理稀疏图时的平方级消耗。对于无向图,每条边会在两个端点的邻接表中各出现一次,因此总存储量为边数的两倍,仍然保持线性级别。

邻接表在边查询上的代价则明显高于邻接矩阵。判断两个顶点是否相邻需要遍历其中一个顶点的邻接表,最坏情况下的时间复杂度与顶点的度成正比。这一特性使得邻接表更适合以遍历为核心操作的应用场景——DFS和BFS在邻接表上的时间复杂度为顶点数加边数之和,这种线性复杂度在处理大规模稀疏图时表现极佳。软考选择题中有时会给出一个具体的图结构,要求考生分析在不同存储表示下DFS遍历路径的差异,这类题目考查的正是对两种存储结构内部数据组织方式的深入理解。邻接表还有一个值得注意的变体是逆邻接表,专门用于有向图中快速查询指向某个顶点的入边,在处理入度相关问题时可以显著降低时间开销,这也是拓扑排序算法中Kahn算法的核心数据结构。

三、深度优先搜索:递归回溯与显式栈的双重视角

深度优先搜索是图遍历中最经典也最能体现回溯思想的算法。它的核心策略可以概括为一条路走到黑:从起始顶点出发,沿着一条未被访问的边深入访问下一个顶点,再以新顶点为起点继续深入,直到无法继续前进时回溯至上一个顶点,再尝试其他未被探索的分支。这种先纵向深入再横向回退的策略天然适合用递归来实现,因为递归函数的调用栈正好模拟了深入与回溯的过程。

在DFS的执行过程中,维护一个访问标记数组是确保算法正确性的绝对前提。没有访问标记,算法会在包含环路的图中陷入无限循环。访问标记的设置时机直接影响遍历结果:标记应该在顶点即将被访问时设置,而非在顶点出栈或回溯时。如果标记设置过晚,同一个顶点可能被重复加入遍历序列,导致结果中出现冗余。递归实现的DFS代码极其简洁——对当前顶点的每个邻接顶点,若该邻接顶点尚未被访问,则递归调用DFS函数继续探索。递归的深度在最坏情况下可达顶点数减一,这意味着对于深度极大的图,递归实现有导致调用栈溢出的风险。在软考中,这一特性经常与系统栈空间限制结合考查,要求考生评估递归方案在特定图结构下的可行性。

显式栈实现的DFS将递归改为手动维护一个栈结构,本质上与递归版本等价,但提供了对遍历过程的更精细控制。先将起始顶点入栈并标记已访问,然后进入循环:弹出栈顶顶点作为当前访问节点,遍历其所有邻接顶点,将尚未访问的邻接顶点入栈并标记。需要注意的是,显式栈版本的遍历序列可能与递归版本不完全相同,这取决于邻接顶点的入栈顺序——由于栈的后进先出特性,先入栈的邻接顶点反而会被后访问。软考真题中频繁出现的给定图结构和起始顶点写出DFS遍历序列类题目,正是对考生理解遍历顺序确定性的考查。深度优先搜索同时会生成一棵DFS生成树,树上的边对应遍历过程中第一次发现某个顶点时所经过的边,而其余的边——连接两个已在DFS生成树上的顶点但未被用作发现新顶点的边——被称为回边或交叉边,它们的出现表明图中存在环路。在无向图中,如果DFS遍历过程中遇到一条指向已访问顶点但不是其父顶点的边,即可判定该图包含环。

四、广度优先搜索:队列驱动的逐层扩张

广度优先搜索采用与DFS截然不同的逐层推进策略。从起始顶点开始,先访问所有与起始顶点距离为一跳的顶点,再访问距离为两跳的顶点,以此类推,形成以起始顶点为中心的同心圆式扩展。这种层次化的遍历逻辑使得队列成为BFS最自然的辅助数据结构——当前层的顶点依次出队进行处理,同时将它们发现的下一层顶点依次入队,由此保证严格的层序性。

BFS的队列操作流程极为规整:起始顶点入队并标记已访问,进入循环后队首顶点出队,遍历其所有邻接顶点,对每个尚未被访问的邻接顶点执行标记、访问和入队三个动作。一旦队列为空,意味着从起始顶点可达的所有顶点均已访问完毕。与DFS的一个重要区别在于,BFS的遍历序列具有更强的确定性——只要图的存储结构和起始顶点固定,BFS序列就完全确定,而不同实现方式下的DFS序列则可能因邻接顶点的处理顺序不同而产生差异。这一特性使得BFS成为软考中求遍历序列类题目的稳定考点。此外,BFS的空间开销通常高于DFS,因为BFS需要将一整层的顶点同时保留在队列中,在最坏情况下队列可能容纳图中接近半数的顶点。对于宽度极大的图,这是一个不可忽视的约束条件。在DFS中,递归调用栈的深度虽然可能很大,但任意时刻栈中只保存从起始顶点到当前顶点的单条路径上的顶点,空间效率相对更高。

BFS与无权图最短路径的等价关系

BFS的一个被广泛低估的深层性质是:在无权图中,BFS从起始顶点到任意顶点的距离恰好等于从起始顶点到该顶点的最短路径长度。这一结论的成立依赖于BFS按层次扩展的特性——当算法首次发现某个顶点时,该顶点所处的层次编号最小,而在无权图中层次编号即等价于最短路径的边数。这与带权图中需要Dijkstra算法或Bellman-Ford算法来求最短路径的场景形成鲜明对比。理解这个等价关系可以帮助考生在软考题目中快速判断:若题干问的是在无权图中求最短路径,可以直接联想到BFS;若在有权图中,则BFS不能保证找到最短路径。命题人偶尔会在这一细节上设置陷阱——给出一张带权图的BFS遍历结果,却诱导考生将其误认为最短路径序列。更为隐蔽的陷阱是:当图中所有边的权值相等时,即使图被标记为带权图,BFS仍然可以找到最短路径,此时Dijkstra算法退化为BFS,而审题不仔细的考生可能误选Dijkstra作为唯一正确答案,忽略了BFS在这种特殊情况下的等效性。

BFS的另一个延伸应用是求连通分量。对于非连通图,单次BFS只能遍历起始顶点所在连通分量中的所有顶点。要遍历全图的所有顶点,需要在外层添加一个循环,依次检查每个顶点是否已被访问,对尚未访问的顶点启动新一轮BFS。外层循环的次数恰好等于图的连通分量个数,这一结论在软考中多次直接作为选择题的考点出现。此外,BFS还可以用于求解无向图的二分图判定——在BFS过程中交替为相邻顶点标记两种颜色,若在遍历中遇到两个相邻顶点被标记为同色,则该图不是二分图。

五、遍历算法的常见误区与软考陷阱

图遍历看似操作简单,但软考命题人在此设置了大量的精细化陷阱,稍有疏忽就会掉入扣分点。第一个高频误区是对非连通图的不完整遍历。许多考生的思维定式是给一个图一个起点就能遍历完,但实际上如果图不是连通的,从单个起点出发的DFS或BFS最多只能覆盖该起点所在的连通分量,其余孤立顶点或被分割在其他连通分量中的顶点不会被访问。解决方法是外层加循环遍历所有顶点,确保没有任何顶点被遗漏。软考真题中曾多次出现给出一个非连通图并要求写出完整遍历序列的题目,忽略外层循环的考生会直接丢失全部分数。更深一层的陷阱在于:有些题目虽然给出了连通图,但要求从多个起点分别进行遍历并比较结果,此时每轮遍历之间必须重置访问标记数组,否则第二轮遍历会因为标记未清零而直接返回空序列。

第二个常见陷阱是混淆DFS递归实现与显式栈实现的遍历序列差异。递归实现的DFS在处理邻接顶点时遵循递归调用的自然顺序,而显式栈由于后进先出的特性,如果入栈顺序与递归的调用顺序相同,得到的遍历序列会被反转。许多考生在不加区分地套用某一种实现的序列去验证另一种实现的输出,必然得出错误结论。软考的选择题和简答题中都会考查这一点,要求考生清楚区分两种实现的差异并准确写出各自的遍历结果。简而言之,递归版的邻接顶点访问顺序是自然顺序从左到右逐个深入,而显式栈版需要将邻接顶点按相反顺序入栈才能获得与递归版相同的遍历序列。

第三个误区涉及访问标记的设置时机。如果标记设置在顶点出队或出栈时而非入队或入栈时,同一个顶点可能在队列或栈中多次出现,尽管最终遍历序列不会遗漏顶点,但会引入冗余操作和效率损失。在考查算法执行过程的题目中,这种入栈即标记与出栈才标记的区别可能导致完全不同的中间状态,命题人经常利用这一差异来设计干扰选项。举例来说,若一个顶点同时是多个已访问顶点的邻居,出栈才标记的策略会将其多次压入栈中,虽然第一次出栈后会被正确标记从而跳过后续的重复弹出,但栈中瞬时容量的膨胀可能误导考生对算法空间消耗的判断。

第四个陷阱与邻接表的顶点排列顺序有关。在实际题目中,当给定图的邻接表表示时,每个顶点的邻接链表中的顶点排列顺序会影响DFS和BFS的具体遍历序列。例如,若顶点A的邻接表为B、C、D,与邻接表为D、C、B,尽管都是访问A的邻居,但DFS的深入顺序和BFS的入队顺序都会随之改变。这一细节在真题中经常被用来制造不同邻接表顺序导致不同遍历序列的辨析题,考查考生对存储结构影响算法行为的敏感度。应对策略是:在作答时严格根据题目给出的邻接表顺序,从左到右或从上到下依次处理邻接顶点,不凭借直觉跳转或重新排序。

六、备考策略与高分答题要点

回顾软考历年真题中图遍历相关的命题规律,可以提炼出几条高效的备考策略。首先,建立存储决定效率的意识。在任何涉及图算法的题目面前,第一步不是思考算法本身,而是判断图的类型——是稠密图还是稀疏图,从而决定选择邻接矩阵还是邻接表。这个判断会辐射到后续所有子问题的解答中,因为不同存储结构下的时间复杂度和空间复杂度表达式截然不同,命题人通常会在选项中将两种结构的时间复杂度混排,测试考生是否具备根据存储方式选择正确复杂度的能力。

其次,DFS和BFS的时间复杂度需要精准记忆而非模棱两可。使用邻接矩阵时,两种遍历均为N的平方级别,因为每个顶点都需要遍历整行来判断邻接关系;使用邻接表时则为顶点数加边数之和的线性级别。这个差异是选择题的固定考点,出现频率极高。此外,空间复杂度也不能忽视——DFS的递归深度在最坏情况下可达顶点数,这既是算法的理论边界,也直接关系到实际编程中的栈溢出风险。对于BFS,空间复杂度取决于图的最大宽度,在极端情况下可能是顶点数的量级。

第三,遍历序列的规范性书写是拿分的关键。考试中要求写出遍历序列时,必须严格遵守从起始顶点出发、按邻接顶点的给定顺序依次访问的规则,不能凭直觉随意跳转。对于DFS,遇到分叉时按邻接表中排列的先后顺序选择优先深入的分支;对于BFS,同一层的顶点按入队先后顺序依次出队。一旦偏离这些规则,即使遍历结果覆盖了所有顶点,也会因序列不一致而失分。建议考生在草稿纸上画出示意图并逐层标注访问顺序,确保每一步都有据可依。

第四,善用遍历算法作为更复杂图问题的基础工具。判断无向图的连通性只需执行一次DFS或BFS后检查访问标记数组是否全部为真;求连通分量数量只需在外层循环中统计启动遍历的次数;判断图中是否存在环路可以通过DFS检查是否遇到回边;拓扑排序可以借助DFS的完成时间逆序或BFS的入度递减策略来实现。理解这些衍生应用的本质联系,是将零散知识点串联为完整知识体系的必经之路,也是在面对综合性大题时快速定位解题方向的核心能力。备考时建议将遍历算法与这些应用场景交叉复习,每学完一个新的图算法就问自己:这个算法的底层遍历逻辑用的是DFS还是BFS?为什么选这种而不是另一种?这种追问式的学习方法能在考试现场帮助形成准确的直觉判断。


以上就是图的深度优先搜索与广度优先搜索遍历机制的完整解析。从图的存储结构选择,到DFS的递归与栈实现,再到BFS的队列驱动与无权图最短路径性质,每一层知识都有其不可替代的位置。软考题目不会孤立地考查某一个知识点,而是要求考生在存储方式、遍历算法、时间复杂度和实际应用场景之间建立快速准确的映射关系。反复练习真题中的遍历序列书写题和复杂度判断题,将存储决定效率内化为解题的默认思维起点,图的遍历这一章节便不再是横亘在通过分数线前面的障碍,而是一座已经被踏实的台阶。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员古德 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 图的DFS与BFS遍历机制详解:存储结构到算法实现
    • 一、图的形式化定义与基本术语
    • 二、图的存储结构:时间与空间的博弈
      • 邻接矩阵的数组映射与效率边界
      • 邻接表的链式组织与空间优势
    • 三、深度优先搜索:递归回溯与显式栈的双重视角
    • 四、广度优先搜索:队列驱动的逐层扩张
      • BFS与无权图最短路径的等价关系
    • 五、遍历算法的常见误区与软考陷阱
    • 六、备考策略与高分答题要点
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档