
简介本资源是一份面向计算机专业本科生及数据结构初学者的图存储结构实践教学材料聚焦邻接矩阵与邻接表两种核心表示方法的原理对比、相互转换及C语言实现。资源以完整可运行的实验代码为核心涵盖有向图的邻接矩阵定义、邻接表结构体设计、MatToList与ListToMat双向转换函数、邻接矩阵/邻接表的打印输出功能并附带详细实验目的、问题描述、实现要求与手动输入扩展说明。压缩包为1个55KB的docx文档内含结构清晰的实验报告正文、完整源码含注释、关键算法逻辑解析及运行示例截图便于读者理解内存分配、链表头插法、二维数组遍历等底层操作细节。目前已有8612人学习下载适合用于课程实验复现、期末复习巩固或算法岗笔试前的图基础强化训练。1. 图的存储不是“存图”而是给关系建一张可查、可走、不卡顿的交通地图你有没有试过在手机地图里搜“从A地到B地怎么走”结果等了三秒才弹出路线那背后很可能就藏着今天要讲的两个词邻接矩阵和邻接表。它们不是什么高深算法而是图这种数据结构在电脑里“安家落户”的两种基本户型——一个像全市统一规划的网格状地铁线路图横纵坐标全是站名交叉点标着“有/无直达线”另一个像每个地铁站自己贴的“本站可换乘”小纸条1号线站口贴着“去西站、去北广场、去机场”2号线站口另贴一版。为什么非得搞两种因为现实中的“关系网”千差万别有的图密得像蜂巢比如朋友圈里人人互关用网格图查“张三和李四熟不熟”快如闪电有的图稀得像沙漠里的绿洲比如全国高速路网300个地级市只连了不到500条高速再画满600×600的表格99%格子都填“没路”纯属浪费内存。学生做实验时总卡在“为什么转换后输出乱码”“为什么邻接表打印不出边”其实问题不在代码而在没想清你手里的图到底是个“高密度老城区”还是个“低密度开发区”选错了户型房子盖得再漂亮住着也憋屈。这份《数据结构学习--图的邻接矩阵和邻接表存储》资源就是一套带施工图、验房清单和踩坑笔记的“图结构精装房交付包”。2. 邻接矩阵用一张固定尺寸的方格纸把所有可能的关系都摊开写死2.1 它长什么样——二维数组就是它的身份证邻接矩阵的本质是一张顶点数×顶点数的方格纸。假设图里有6个顶点编号0到5那这张纸就是6行6列的二维数组arcs[6][6]。每个格子arcs[i][j]的含义非常直白从顶点i出发能不能一步走到顶点j如果是无权图填1表示“能”0表示“不能”如果是有权图比如带距离、耗时、费用就填具体数值比如arcs[0][3] 7表示“从0号点到3号点边权是7”空缺或不存在的边按惯例填一个极大值如代码里的INF 32767或-1避免和有效权值混淆。提示代码中#define MAX_VERTEX_NUM 20是预留的“最大户型面积”。实际图只有6个点但数组仍按20×20分配——这是为扩展性留的余量不是浪费。就像买房签合同写“建筑面积120㎡”实际得房率可能80%但承重墙位置早定死了。2.2 它最擅长什么——三类操作快得像条件反射邻接矩阵的强项全来自它“地址固定、索引直达”的物理结构查边存在性O(1)问“0号点和3号点之间有边吗”——直接看arcs[0][3]是不是0或INFCPU一次内存寻址搞定比翻通讯录找人还快。改边权O(1)把“0→3”的距离从7改成9arcs[0][3] 9一行赋值毫无拖泥带水。遍历所有边O(n²)需要统计全图有多少条边把整个n×n表格扫一遍加总非零/非INF元素即可。虽然慢但逻辑极简不怕漏。// 示例快速判断两点是否连通邻接矩阵核心能力 int isEdgeExist(MGraph *g, int i, int j) { if (i 0 || i g-vexnum || j 0 || j g-vexnum) return 0; // 越界检查防玄学崩溃 return (g-arcs[i][j] ! 0 g-arcs[i][j] ! INF); }这段代码没有循环、没有递归就是一次数组下标访问。它之所以快是因为CPU知道arcs[i][j]在内存里的绝对地址base_address i * n jn是顶点数硬件直接算出位置伸手就拿。2.3 它的硬伤在哪——空间和稀疏度的生死局优势有多亮短板就有多刺眼。邻接矩阵的空间占用是刚性的O(n²)100个顶点 → 占用10,000个整数空间10,000个顶点 → 占用100,000,000个整数空间约400MB纯数据还不算指针、结构体开销但现实中绝大多数图是稀疏图——边数e远小于n²。比如一个含1000个城市的交通网若平均每个城市只连3条高速总边数才3000而邻接矩阵却要塞满1,000,000个格子其中99.7%是“没路”的占位符INF或0。这就像为一个只有3户人家的村子强行修建覆盖1000平方公里的全光缆网络——钱花了维护难了扩容还卡在物理路由上。注意代码中DispMat()函数打印时用---代替INF正是为了让人眼直观识别“无效边”。这个细节暴露了邻接矩阵的底层焦虑它必须用特殊标记填满所有空格否则无法维持“方格纸”的完整性。3. 邻接表给每个顶点发一本专属通讯录只记真有联系的人3.1 它长什么样——数组链表的混合双打如果说邻接矩阵是“全局统一分配的网格本”邻接表就是“每人一本活页通讯录”。它的结构分两层顶层是顶点数组vertices[n]大小等于顶点数n每个元素是一个VNode结构体存顶点自身信息如名称、ID和一个关键指针firstarc底层是边链表每个VNode的firstarc指向一条单向链表链表节点ArcNode里只存两样东西adjvex这条边指向的邻居顶点编号、info边权如距离/费用以及nextarc指向下一条边。// 邻接表核心结构体精简版 typedef struct ArcNode { // 边节点记录“我连谁”和“代价多少” int adjvex; // 邻接点下标比如指向顶点3 int info; // 边的信息权重、类型等 struct ArcNode *nextarc; // 指向下一个邻接点 } ArcNode; typedef struct VNode { // 顶点节点记录“我是谁”和“我的通讯录在哪” VertexType data; // 顶点数据如城市名北京 ArcNode *firstarc; // 指向第一条边的指针通讯录首页 } VNode; typedef struct { // 整张图顶点数组 元信息 AdjList vertices; // 顶点数组n本通讯录 int vexnum, arcnum; // 顶点数、边数 } ALGraph;这个设计的精妙在于空间随实际边数线性增长O(ne)。1000个城市、3000条高速顶点数组占1000个槽位边节点链表共3000个节点总空间≈4000个结构体比邻接矩阵省99%以上。3.2 它最擅长什么——遍历邻居和增删边的灵活身段邻接表的优势源于它“按需分配、动态链接”的基因遍历某顶点的所有邻接点O(degree(v))问“北京能直达哪些城市”——顺着vertices[0].firstarc的链表一路往下读读到nextarc NULL为止。时间只和北京连出的边数度数有关与全图规模无关。插入/删除一条边O(1)均摊给北京新增一条到雄安的高速只需malloc一个新ArcNode填好adjvex雄安编号、info距离然后把它插到vertices[北京编号].firstarc链表头部头插法。删除同理找到目标节点前驱修改指针即可。节省内存对稀疏图近乎完美没有冗余格子每字节都存有效信息。调试时打印DispAdj()看到的是一行行清晰的“0: 3 5”0号点连向3、5号点而不是满屏的0 0 0 7 0 0。// 示例向顶点i添加一条指向j的边邻接表核心操作 void AddEdge(ALGraph *G, int i, int j, int weight) { ArcNode *p (ArcNode*)malloc(sizeof(ArcNode)); p-adjvex j; p-info weight; p-nextarc G-vertices[i].firstarc; // 头插法新节点成为首节点 G-vertices[i].firstarc p; }这段代码里p-nextarc G-vertices[i].firstarc和G-vertices[i].firstarc p的顺序不能颠倒否则会丢失原链表。这是邻接表操作的经典“断链-重连”手法也是初学者最容易翻车的血泪经验点。3.3 它的软肋在哪——查边存在性成了“大海捞针”邻接表的灵活性是以牺牲随机访问为代价的。最痛的短板就是查任意两点间是否有边平均要O(degree(i))时间。问“北京和上海之间有直达高铁吗”——得翻开北京的通讯录vertices[0].firstarc从第一页开始逐条比对adjvex直到找到adjvex上海编号或翻到末页nextarc NULL。如果北京连了200个城市就得比对最多200次如果图是星型结构一个中心点连所有其他点查中心点到任一叶节点是O(1)但查两个叶节点之间答案永远是“没有”可你得把中心点的200页通讯录全翻完才能确认。这就是为什么邻接表不适合做“全局关系快查”。它像一个高效的快递分拣站知道某个包裹该发往哪遍历邻居快但若问“A仓和B仓之间能否直送”得先查A仓的发货单、再查B仓的收货单效率远不如一张全局航线图邻接矩阵上直接看交叉点。4. 邻接矩阵 vs 邻接表五种常见误区专治“我以为它们差不多”4.1 误区一邻接表一定比邻接矩阵“高级”或“现代”为什么混教程常强调邻接表“节省空间”学生误以为“新好”写作业时盲目选邻接表。怎么区分高级与否取决于场景。在稠密图如棋盘上所有格子互连的图e ≈ n²中邻接矩阵的O(1)查边能力碾压邻接表的O(n)遍历且矩阵支持矩阵运算如求路径数A²的i,j元素表示长度为2的路径数邻接表无法直接实现。选型不是比新旧而是比“你的图更常被怎么问问题”。4.2 误区二邻接矩阵只能存有向图邻接表才能存无向图为什么混看到邻接矩阵代码里arcs[i][j]和arcs[j][i]值不同有向就认为它“不支持”无向。怎么区分无向图在邻接矩阵中体现为对称矩阵——只要arcs[i][j] arcs[j][i]如都填1或都填权重它就是合法的无向图存储。邻接表存无向图则需双向建边i的链表加jj的链表加i。两者都能存只是无向图下邻接矩阵天然对称邻接表需手动保证双向性。4.3 误区三邻接表的“链表”必须用指针不能用数组模拟为什么混教材代码用malloc和struct ArcNode*学生以为“链表动态内存必须指针”。怎么区分完全可以用静态链表数组游标实现邻接表例如定义ArcNode edgePool[MAX_ARC_NUM]和int head[MAX_VERTEX_NUM]存每个顶点链表头节点下标用edgePool[i].next代替指针。这样避免频繁malloc/free适合嵌入式或实时系统。指针只是最直观的实现方式不是本质约束。4.4 误区四转换程序MatToList / ListToMat只是“格式搬运”没有业务价值为什么混实验要求“写两个转换函数”学生当成机械编码任务忽略其工程意义。怎么区分真实系统中数据源常是异构的——GIS系统导出的可能是邻接矩阵CSV而算法库要求邻接表输入。转换函数就是数据管道的适配器。更关键的是MatToList中的for(jn-1;j0;j--)是逆序遍历代码注释为“头插法”这决定了链表中边的顺序影响DFS/BFS遍历起点选择。转换不是复制是重构关系表达。4.5 误区五邻接矩阵的INF32767可以随便改成其他大数不影响逻辑为什么混看到#define INF 32767觉得“反正很大就行”改成999999或0x3f3f3f3f。怎么区分INF是“无穷大”的语义占位符用于初始化和比较。若后续算法涉及min(dist[i], dist[k]weight)INF必须满足INF weight INF数学上无穷大有限数无穷大。32767是16位有符号整数最大值32767 1会溢出成负数破坏语义正确做法是用足够大的数如INT_MAX/2或专门的布尔标志位。代码中if(g-arcs[i][j]INF)的判断一旦INF溢出逻辑就崩了。5. 什么时候用邻接矩阵什么时候用邻接表——一张决策表和三个真实场景5.1 核心决策表看图的“密度”和你的“高频操作”判断维度优先选邻接矩阵优先选邻接表图的边数 e 与顶点数 n 关系e 接近 n²稠密图如社交网络全连接子图、小规模全连通图e 远小于 n²稀疏图如互联网网页链接、大规模交通网、电路布线最常做的操作频繁查询任意两点间是否有边/边权如Floyd最短路径、连通性批量检测频繁遍历某点所有邻居如BFS/DFS搜索、PageRank迭代、推荐系统协同过滤内存是否敏感内存充足或图规模小n1000内存受限或图规模大n10000且 e/n 10平均度数低是否需要矩阵运算需要计算 A²、A³路径数、特征值图谱理论不涉及线性代数运算纯图遍历逻辑这张表不是教条而是帮你把模糊感觉“好像该用邻接表”转化成可验证的条件。比如面试官问“10亿用户的关系图怎么存”——先算密度微信好友平均300人e ≈ 10⁹ × 300 3×10¹¹n² 10¹⁸e/n² 3×10⁻⁷是极致稀疏图再看操作推荐系统要查“用户A的好友的好友”本质是两层邻居遍历。结论邻接表甚至要升级为邻接多重表或图数据库。5.2 场景一校园网拓扑监控——邻接矩阵的“闪电响应”时刻某高校有200台核心交换机n200任意两台间都有光纤直连e ≈ 200²40000稠密图。运维系统需每秒检测“交换机A和B是否链路中断”。若用邻接表每次查询都要遍历A的200条链路找B的编号平均100次比较200台×200查询40000次/秒CPU狂转。若用邻接矩阵arcs[A][B] 0 ? 断 : 通一次内存读取200×20040000次查询在纳秒级完成。这里邻接矩阵的O(1)查边是保障SLA服务等级协议的生命线。实验代码中DispMat()打印的整齐方阵正是运维大屏上实时闪烁的链路状态图。5.3 场景二短视频推荐引擎——邻接表的“关系挖掘”主场某平台有5亿用户n5×10⁸平均每人关注200人e ≈ 10¹¹稀疏图。推荐算法需执行对用户A找出其关注者B再找出B关注的C计算A-C的相似度。邻接矩阵内存需求 (5×10⁸)² × 4字节 ≈ 10¹⁸ 字节10亿TB宇宙中没有服务器能装下。邻接表A的链表200个节点B的链表200个节点两次链表遍历总操作数≈200×20040000毫秒级完成。邻接表在此不是“够用”而是“唯一可行”。实验中MatToList()的头插法看似微小实则决定了“最近关注的用户排在链表前面”让推荐优先考虑时效性高的关系——这是业务逻辑对数据结构的反向塑造。5.4 场景三编译器符号表管理——混合策略的生存智慧某C语言编译器需管理函数调用关系。项目有10000个函数n10⁴但平均每个函数只调用5个其他函数e5×10⁴稀疏图。然而编译器最常做的操作是快速判断func_A是否直接调用func_B查边同时要列出func_A调用的所有函数遍历邻居。纯邻接矩阵空间10⁸整数400MB查边O(1)但遍历需扫10⁴列纯邻接表空间≈1.5×10⁴节点遍历O(5)但查边平均O(2.5)。最优解是“邻接矩阵邻接表”混合用邻接表存调用关系主结构再用一个10⁴×10⁴的布尔矩阵仅1bit/格总空间12.5MB缓存查边结果。空间增加不多却把高频查边从O(5)降到O(1)。这印证了实验资源的价值MatToList和ListToMat不是孤立函数而是让你理解结构可转换、能力可组合——真正的工程高手从不迷信单一结构。从那以后我每次设计图结构都强制走一遍这个流程先手算图的e/n²密度比再列出未来三个月最可能被调用的3个API查边遍历改权最后对照决策表圈出候选方案。哪怕最终选了邻接表也会在备注里写清“因查边操作将由缓存层承担故此处邻接表不构成性能瓶颈”。希望帮到你。本文还有配套的精品资源点击获取