PTA图遍历核心:邻接矩阵与邻接表存储及DFS/BFS实战详解
1. 图的两大存储方案邻接矩阵与邻接表的选择逻辑说实话每次在PTA上碰到图的题目很多同学的第一反应是直接开干结果往往在存储方式这一步就走错了方向。我自己带过不少准备天梯赛和数据结构课程设计的同学发现一个共同规律存储结构选对了遍历代码写起来顺手调试也快选错了后面全是坑甚至会在DFS递归时被爆栈问题整得怀疑人生。今天这篇不讲虚的就从图的存储和遍历这个地基聊起把PTA上反复出现的考点彻底拆明白。1.1 邻接矩阵什么时候无脑选它邻接矩阵的思路非常朴素——用一个二维数组G[i][j]来记录顶点i和顶点j之间的关系。有边就是1或者权重无边就是0。这个方案最大的优点就是结构简单、代码直白判断任意两个顶点之间是否有边只需要O(1)时间。但它的代价也相当显眼空间复杂度是O(V²)其中V是顶点数。这意味着当顶点数达到10000时单单存储一个int型的二维数组就需要约400MB内存10000×10000×4字节这在PTA的OJ判题环境下几乎是必炸的。我的个人经验是在PTA上遇到以下情况时优先考虑邻接矩阵顶点数V较小通常在1000以内比如V≤500时邻接矩阵完全无压力题目中需要频繁判断两点之间是否有边比如最短路径类问题图比较稠密边的数量接近V²的量级。举个典型例子PTA里经常出现的图着色问题或者判断连通性的简单版本顶点数通常控制在100以内这时候用邻接矩阵写起来非常舒服#include cstdio #include cstring #define MAXV 105 int G[MAXV][MAXV]; int vis[MAXV]; void DFS(int u, int n) { vis[u] 1; printf( %d, u); for (int v 1; v n; v) { if (G[u][v] !vis[v]) { DFS(v, n); } } } int main() { int n, m; scanf(%d %d, n, m); memset(G, 0, sizeof(G)); memset(vis, 0, sizeof(vis)); for (int i 0; i m; i) { int u, v; scanf(%d %d, u, v); G[u][v] G[v][u] 1; // 无向图 } for (int i 1; i n; i) { if (!vis[i]) { DFS(i, n); printf(\n); } } return 0; }这里有个非常实用的细节如果题目中顶点编号是从1开始而不是从0开始我通常就直接把数组开到MAXV 5然后从1开始循环省去下标减一的麻烦也能避免因为忘记转换而出现的越界错误。这个习惯看起来小但真能帮你避开不少低级扣分。1.2 邻接表稀疏图的必选项当顶点数达到10⁵级别边数只有10⁵到10⁶时邻接矩阵就完全不可行了必须改用邻接表。邻接表的本质是对每个顶点维护一个链表或者vector里面存放所有与该顶点直接相连的邻居节点。空间复杂度降到O(VE)这也是PTA绝大多数图论题目的标准做法。在C里最推荐的方式是用vectorint adj[MAXV]或者vectorvectorint adj原因很简单vector帮你管理内存不需要手动写链表节点也不容易发生指针错误。我在PTA上写邻接表遍历的模板大致是这样#include cstdio #include vector #include queue #include algorithm using namespace std; const int MAXV 100005; vectorint adj[MAXV]; int vis[MAXV]; void DFS(int u) { vis[u] 1; printf( %d, u); // 按照编号从小到大访问 sort(adj[u].begin(), adj[u].end()); for (int v : adj[u]) { if (!vis[v]) DFS(v); } } void BFS(int start) { queueint q; q.push(start); vis[start] 1; while (!q.empty()) { int u q.front(); q.pop(); printf( %d, u); for (int v : adj[u]) { if (!vis[v]) { vis[v] 1; q.push(v); } } } }注意上面代码里我对邻接表做了sort这不是多余的。PTA许多遍历题目的输出要求按编号递增顺序访问邻接点如果输入边的时候没有按顺序就必须在遍历前排序。这个排序动作看起来增加了O(E log E)的复杂度但在大多数题目的数据范围内完全可行而且能直接避免输出顺序错误导致的WA。2. 深度优先遍历递归思想在OJ里的真实面貌2.1 DFS与二叉树的前序遍历是一家人很多同学在学DFS时觉得图里的DFS很抽象但如果在学二叉树时理解透彻了先序遍历根左右图里的DFS其实只是把二叉树中每个节点最多有两个孩子扩展成了每个节点可以有任意多个邻居。我之前在博客里反复强调过DFS本质上是沿着一条路走到黑走不动了再退回来的策略。在递归实现中系统栈帮我们维护了路径回溯的过程在显式栈实现中自己压栈的元素则记录了下一步该从哪里继续。PTA里常见的DFS题型包括列出连通集判断是否存在环拓扑排序的DFS实现等。其中列出连通集题目算是DFS最直白的应用它要求从编号最小的顶点开始深度优先搜索并输出所有连通分量。这个题需要注意的点在于连通分量之间是独立的遍历完一个连通分量后还要循环去找下一个未被访问的顶点直到所有顶点都被访问过。2.2 递归的代价从爆栈到显式栈我在PTA上曾经遇到过一道遍历题数据范围写到V50000递归DFS直接导致栈溢出程序运行到一半就异常终止。那次经历给我留下了深刻印象OJ环境的栈大小通常是有限的往往在8MB左右深层递归很容易秒爆。解决方案有两种一是增大递归深度限制某些OJ支持通过编译器参数调整但PTA上未必可靠二是将递归DFS改为显式栈的迭代DFS。迭代版代码如下void DFS_iter(int start) { stackint st; st.push(start); vis[start] 1; while (!st.empty()) { int u st.top(); st.pop(); printf( %d, u); for (int v : adj[u]) { if (!vis[v]) { vis[v] 1; st.push(v); } } } }不过这里要提醒一下显式栈版本的DFS和递归版本的DFS在访问顺序上可能有差异。递归版本是入栈前就被访问而显式栈版本如果只在pop时输出就变成了类似后进先出首次扩展的顺序有时会和题目预期的输出顺序不一致。如果遇到顺序WA可以在push时标记vis但用另一个数组记录访问顺序也可以把邻接点逆序压栈确保pop出来的顺序与递归版本一致。这个细节非常值得在调试过程中专门验证不能想当然。这里提醒一下新手不要一上来就写递归因为递归的思维负担最小但如果题目的V大到10⁵以上一定要有意识地评估递归深度。可以先提交一次递归版本试探如果收到段错误或者非零返回再换成显式栈版本。3. 广度优先遍历从队列到层序思维3.1 BFS为什么天然契合最少步数问题BFS广度优先搜索的核心是从起点出发一层一层往外扩散。这个策略恰好满足无权图最短路径的需求第一次访问到某个顶点时经过的边数一定是最少的。所以PTA里六度空间社交网络图中好友关系层数统计等题目本质上都建立在BFS之上。我在之前的专栏里不止一次写过BFS实现的关键是队列。起点入队队首出队时把它的所有未访问邻居入队如此往复直到队列为空。因为队列的FIFO特性同深度的顶点一定会先于更深层的顶点被访问这也是层序直觉的来源。用BFS遍历时有几点经验值得分享入队的同时就要标记访问否则同一个节点可能会被多个邻居重复入队造成死循环或者输出重复如果需要输出层数可以在队列里同时保存节点和层级信息也可以用当前层计数的方式分层统计对于无向图BFS和DFS一样同样需要在遍历完后检查所有顶点确保所有连通分量都被覆盖。3.2 邻接表BFS的PTA格式细节PTA对BFS输出的格式卡得非常严格行尾不能有多余空格每个顶点的访问顺序必须完全正确。我在列出连通集题目中踩过一次坑因为忘了对邻接表排序输出的访问顺序和题目要求不一致白白浪费了三次提交。正确的做法是在BFS启动之前先对每个顶点的邻接表做一次排序。如果边的输入顺序已经是升序这一步可以省略但如果输入是乱序的不做排序必错。另外一个输出细节是第一个数前没有空格后续数前有空格这个用标志变量控制bool first true; void print(int x) { if (first) { printf(%d, x); first false; } else printf( %d, x); }这个方法比起先拼字符串再去除末尾空格要干净得多也避免了字符串操作的额外开销。4. PTA判题视角输入输出格式与常见扣分陷阱4.1 理解OJ的黑盒测试本质PTA的判题方式和其他在线评测系统一样都是黑盒测试你的程序读取标准输入产生标准输出然后用一组测试数据的预期输出和你的输出做逐字节比对。这意味着任何多余的字符、缺失的空格、错误的换行都会导致WA。我特别想建议初学者做一件事在本地调试时一定要自己构造至少三组测试数据分别覆盖一个顶点的极端情况非连通图顶点编号从1开始这三种典型场景。不要只在样例数据上通过了就提交因为PTA的隐藏测试数据往往比样例刁钻得多。举个例子对于图遍历题很多同学会忽略图可能完全不连通这个前提导致遍历只处理了第一个连通分量后面的顶点完全没有输出。我在代码里总是习惯在读完所有顶点之后用一层for循环把所有顶点都扫一遍每遇到未访问顶点就启动一次DFS或BFS这样无论图连不连通都能完整覆盖。4.2 几个极易踩中的隐藏扣分点我梳理了一下自己在PTA图遍历题上踩过和帮别人排查过的常见错误列个表供参考问题现象根本原因解决办法输出顺序与样例不符邻接表未按编号排序在遍历前对每个顶点的邻接表排序程序运行超时使用邻接矩阵而顶点数过大改为邻接表存储降低空间与遍历复杂度找不到孤立点没有在主函数里循环访问所有顶点外层加一层遍历检查所有未访问顶点依次作为起点输出末尾多空格格式控制不当使用标志变量控制空格输出段错误递归深度过大或数组下标越界改用迭代DFS检查数组大小及顶点编号边界重复输出节点被多个父节点访问时未及时标记在入队/入栈时立刻标记已访问这个表格里的前四项几乎覆盖了PTA图遍历题80%的WA原因。尤其是末尾空格的问题很多同学觉得多一个空格无伤大雅但在OJ的逐字节比对机制下这就是实打实的错误。这里再补充一点关于邻接矩阵初始化的建议如果是多组测试数据的题目每处理完一组数据后一定要把vis数组和邻接矩阵清零。用memset最快但要注意memset是按字节操作的对int数组清0没问题如果要重置成其他值就得手动循环了。我自己习惯在每组数据的开头调用一次memset避免上一组数据残留的访问标记影响当前结果。5. 从存储到遍历一道完整题目的设计与思考5.1 还原列出连通集的完整解题链路以PTA的经典题列出连通集为例完整走一遍从读题到AC的过程。这道题的要求是给定一个无向图要求先输出DFS的结果再输出BFS的结果每个连通分量输出一行且每个连通分量从编号最小的顶点开始。拿到题目后我建议先画个图把样例输入对应的无向图在纸上画出来然后手写一遍DFS和BFS的预期输出。这个过程看似多余却能帮你把递归/队列的抽象流动具象化是排查逻辑错误最快的手段。核心代码结构如下// 主函数中的连通分量循环 for (int i 0; i n; i) { if (!vis[i]) { first true; DFS(i); printf(\n); } } memset(vis, 0, sizeof(vis)); for (int i 0; i n; i) { if (!vis[i]) { first true; BFS(i); printf(\n); } }这段代码里有几个设计意图值得说明先用DFS遍历所有连通分量输出完毕后再统一重置vis数组确保BFS阶段从零开始不会受到DFS阶段标记的干扰每个连通分量的内部输出前都重置first标志确保换行和空格格式正确外层循环从最小的顶点编号开始天然满足从编号最小的顶点开始这一题目要求。5.2 测试数据自检与常见失误的自我排查AC代码不是一次写出来的我通常会准备下面五组数据来验证只有一个顶点的图输入1 0预期输出只有一行0两个顶点无边输入2 0预期输出两行分别是0和1两个顶点一条边输入2 1 0 1预期输出各一行包含两个顶点三个顶点构成一个三角形预期DFS/BFS输出顺序一致都是0 1 2普通非连通图包含两个连通分量每个分量的大小不同验证循环遍历的正确性。如果这五组数据全部通过基本上这道题的隐藏用例也能过掉七八成了。剩余的边界问题比如顶点编号从1开始只需把数组和循环起点调整即可。在调试过程中我还习惯把DFS和BFS的执行过程打印到本地终端上包括当前访问的顶点当前顶点有哪些邻居哪些邻居已经被访问过。这样能直观看到递归和队列的变化比起单纯盯着代码找错要高效得多。当然这些调试用的打印在提交前必须删掉或者注释掉否则输出格式会多出一堆信息。5.3 从一道题到一类题举一反三的扩展图的存储和遍历是后续所有图算法的基础掌握了这两件事后面的最短路径、最小生成树、拓扑排序、连通分量求解都是在这个地基上盖楼。比如PTA里的六度空间题就是在BFS的基础上额外统计层数不超过6的节点占比图着色问题是要基于邻接矩阵检查每条边的两端颜色是否不同关键活动题则是在拓扑排序的基础上加入事件最早/最晚发生时间的计算。如果存储和遍历写得顺手这些题的核心逻辑其实都不会太难。我的体会是做题不要贪多关键是吃透每一道题背后的数据结构和算法设计思路。把图的两种存储方式、DFS/BFS的两种遍历策略以及它们在PTA判题环境下的各种细节全部搞清楚比盲目刷十道同类型的题收获更大。图这块知识是典型的会者不难难者不会一旦你打通了存储和遍历这个关键的任督二脉后面遇到再复杂的图论题至少不会在起步阶段就卡住。

相关新闻

STM32定时器输入捕获测PWM脉宽:HAL库配置与代码实战

STM32定时器输入捕获测PWM脉宽:HAL库配置与代码实战

1. 为什么建议直接学HAL库的输入捕获很多从标准库转到STM32Cube HAL开发的朋友,第一次接触定时器输入捕获时,最直观的感受是:标准库那套“配置GPIO为复用模式、开定时器时钟、写CCMR/CCER/DIER寄存器、写中断服务函数”的操作,在H…

2026/10/5 8:12:01 阅读更多 →
MMC5603NJ三轴磁力计指南针实现:从I2C驱动到航向角解算

MMC5603NJ三轴磁力计指南针实现:从I2C驱动到航向角解算

做嵌入式这些年,手里过过的传感器少说也有几十种,但但凡涉及姿态、航向类的项目,地磁传感器始终是绕不开的一个角色。MMC5603NJ这块三轴磁力计,最初是朋友推荐给我的,说在无人机、机器人导航和对讲机里用得挺多&#x…

2026/10/5 8:12:01 阅读更多 →
扩散模型精准概念擦除:解决跨编辑干扰的SCIE方法

扩散模型精准概念擦除:解决跨编辑干扰的SCIE方法

1. 这不是“删掉某个概念”,而是让模型学会“不混淆地遗忘”你有没有试过让一个扩散模型忘记“猫”——结果它把“狗”也画得不像了?或者想抹除训练数据里某类特定风格(比如某位艺术家的笔触),模型却开始把所有水彩画都…

2026/10/5 8:12:01 阅读更多 →

最新新闻

STM32H743从25MHz晶振到480MHz主频的完整时钟树配置指南

STM32H743从25MHz晶振到480MHz主频的完整时钟树配置指南

一块板子,外部只有一颗25MHz晶振,要求把STM32H743的主频稳定跑到480MHz。这个需求听起来很基础,但实际操作起来,很多人在CubeMX时钟树这一关就卡住了:要么是PLL参数不对,要么是生成代码后系统跑不到指定频率…

2026/10/5 10:52:44 阅读更多 →
Nmap核心功能与实战:从安装到扫描原理全解析

Nmap核心功能与实战:从安装到扫描原理全解析

搞网络安全和系统运维的朋友,几乎没有不知道Nmap的。它全称Network Mapper,是一款开源免费、功能极其强大的网络扫描与安全审计工具,在“网络扫描”这个场景里,它就是事实上的标准。不管是做资产盘点、端口探测、服务识别&#xf…

2026/10/5 10:52:44 阅读更多 →
FPGA HDMI设计必读:Video PHY Controller IP原理与调试指南

FPGA HDMI设计必读:Video PHY Controller IP原理与调试指南

做HDMI设计,特别是FPGA方案时,很多人会卡在一个地方:明明协议层、像素数据处理都写完了,结果上板之后,屏幕不是雪花就是黑屏。最后查来查去,问题多半出在物理层——也就是Video PHY这一块。这篇我就围绕Vid…

2026/10/5 10:52:44 阅读更多 →
极限计算核心逻辑:直接代入、重要极限与等价无穷小全解析

极限计算核心逻辑:直接代入、重要极限与等价无穷小全解析

“老师,这个极限到底能不能直接带?”我在带高数和考研数学这些年,几乎每周都会收到好几次这样的问题。很多人学到极限这一章,被“两个重要极限”“等价无穷小”“未定式”这几个词绕得晕头转向,做题全靠猜,…

2026/10/5 10:52:44 阅读更多 →
深度学习AMP训练必备:梯度缩放器GradScaler原理与实战排查

深度学习AMP训练必备:梯度缩放器GradScaler原理与实战排查

做深度学习训练的朋友,尤其是跑过大模型、大batch、还嫌显存不够用的人,应该都绕不过一个名词:自动混合精度(AMP)。我第一次接触AMP是在开源检测项目里,打开一行配置训练速度直接上了一个台阶,但…

2026/10/5 10:52:44 阅读更多 →
Vision Transformer图像去雾实战:Patch尺寸与Decoder设计关键

Vision Transformer图像去雾实战:Patch尺寸与Decoder设计关键

简介:本资源是一套基于Vision Transformer(ViT)的图像去雾算法完整实现方案,面向计算机视觉方向的研究生、算法工程师及深度学习实践者,聚焦于恶劣天气下图像质量退化问题的端到端建模与复现。压缩包共340个文件&#…

2026/10/5 10:51:44 阅读更多 →

日新闻

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:22 阅读更多 →
AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

1. 从“plugins”这个词说起:它到底在解决什么问题如果你最近在折腾 AI 编程工具,尤其是 Cursor、Codex CLI、Claude Code 这类带 CLI 的编辑器或命令行助手,那你大概率绕不开一个词——plugins。这个词本身不新鲜,从浏览器到 IDE…

2026/10/5 0:00:23 阅读更多 →
第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:23 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 5:06:42 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 1:10:22 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 3:06:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 11:40:45 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 20:14:29 阅读更多 →