COMP2521作业2完整实现:社交网络图算法C语言源码与自动化测试套件

COMP2521作业2完整实现:社交网络图算法C语言源码与自动化测试套件 本文还有配套的精品资源点击获取简介一套面向COMP2521课程的社交网络图算法实战资源包含从底层数据结构到高层分析功能的全部C语言实现。核心模块涵盖图结构建模Graph.c、优先队列PQ.c、Dijkstra单源最短路径Dijkstra.c、多种中心性度量CentralityMeasures.c、Lance-Williams层次聚类LanceWilliamsHAC.c以及可视化辅助工具GraphVis.c。配套6组标准测试输入1.in–6.in及对应预期输出如1bn.out、4di.out支持shell脚本testCentrality.sh和独立测试文件test*.c进行模块级与集成验证。提供完整头文件Graph.h、PQ.h等、Makefile编译配置、README.md说明文档以及HTML可视化前端seeGraph.html data.js可在Linux环境使用GCC一键构建、运行和调试。所有代码严格遵循课程编码规范适配自动评测流程便于本地开发、功能验证与算法对比。1. 这不是一份“交作业”的代码包而是一套可复用的图算法工程实践模板如果你正在啃COMP2521的作业2或者刚被Graph.c里那个struct GraphRep的内存布局绕晕、在Dijkstra.c里反复调试堆溢出、对着testCentrality.sh跑出的Segmentation fault (core dumped)发呆——别急着删分支重来。我带过三届这门课的助教也亲手重构过五版图算法库这套资源最核心的价值从来不是“帮你拿满分”而是把课程要求的离散模块真正拧成一个能跑、能调、能测、能扩的轻量级图分析系统。它覆盖了社交网络分析中四个不可回避的底层能力层数据建模层Graph→ 调度支撑层PQ→ 路径推理层Dijkstra→ 结构洞察层Centrality HAC。每个.c文件都不是孤立函数堆砌而是按“接口契约—实现细节—错误边界”三层结构组织。比如PQ.c里那个看似简单的PQInsert()实际藏着三个关键设计选择用二叉堆而非斐波那契堆课程约束常数开销更稳、键值比较采用double而非int适配中心性分数精度、插入失败时返回false并errno ENOMEM让上层Dijkstra.c能统一处理内存不足。这些细节不会出现在课程PPT里但你在make test失败十次后一定会亲手补上。关键词里的“Dijkstra算法”“中心性度量”“层次聚类”“优先队列实现”在这里不是名词解释而是可打断、可打印、可替换的活体模块。你可以把CentralityMeasures.c里的betweennessCentrality()换成Brandes算法的优化版本只要头文件CentralityMeasures.h的函数签名不变graphMeasurements工具照样调用也可以把LanceWilliamsHAC.c的链接准则从average切换到complete只需改一行宏定义testHAC.sh就能验证新策略对社区划分的影响。这种松耦合不是靠运气而是靠Makefile里明确的依赖链、头文件里严格的#ifndef GRAPH_H防护、以及每个测试文件只include自己需要的头文件——testDijkstra.c绝不会includeLanceWilliamsHAC.h。适合谁如果你是刚学完链表和指针的学生这份资源能让你第一次看清“图”在内存里真实的样子不是邻接矩阵的二维数组而是VertexNode链表挂载在VList数组上的动态结构如果你正卡在自动评测的timeout错误你会从testPQ.c的基准测试里发现PQDeleteMin()的均摊时间必须压到O(log n)否则6.in的千节点图会直接超时如果你打算用这个基础做课程项目延伸GraphVis.c生成的data.js格式就是为seeGraph.html定制的你甚至可以把它喂给D3.js画力导向图。它不教你C语言语法但它强迫你用C的方式思考内存、边界和契约——这才是COMP2521真正想塞进你脑子里的东西。2. 整体架构与模块协同逻辑为什么这样拆分而不是写成一个大main函数2.1 四层抽象模型从物理存储到语义分析这套实现没有采用“所有功能塞进main.c”的野路子而是严格遵循数据结构→算法支撑→核心算法→分析应用的四层递进。这不是为了炫技而是课程自动评测系统Autograder的硬性要求每个模块必须提供独立接口且编译目标.o文件可单独链接。我们来拆解这个分层如何解决实际问题第一层Graph.c / Graph.h —— 图的物理存在社交网络不是数学概念是内存里的字节。Graph.c用struct GraphRep封装了三种表示法邻接表VList *vertices、边列表Edge *edges、顶点数组Vertex *vArray。关键设计在于延迟初始化GraphNew()只分配顶点数组邻接表节点在GraphInsertEdge()时才malloc。这避免了稀疏图如6.in的1000节点仅2000边浪费99%内存。而GraphFree()必须按反向顺序释放先free所有VList节点再freevertices数组最后freeGraphRep本身——漏掉任何一层都会导致Valgrind报definitely lost。第二层PQ.c / PQ.h —— 算法的“心脏起搏器”Dijkstra和HAC都需要动态选取最小/最大元素。PQ.c实现的是最小堆min-heap因为Dijkstra要取距离最小的未访问节点HAC要取相似度最大的簇对。这里有个易错点课程要求PQ支持重复键值同一顶点可能多次入队所以PQInsert()不做去重而PQDeleteMin()必须处理堆内多个相同键值的情况。PQ.c用void *item泛型指针存储数据配合PQCompareFn函数指针让上层无需关心具体类型——Dijkstra.c传dist[v]LanceWilliamsHAC.c传similarity[i][j]底层堆操作完全一致。第三层Dijkstra.c / CentralityMeasures.c / LanceWilliamsHAC.c —— 核心算法引擎这三层共享一个设计哲学输入隔离输出契约化。Dijkstra.c的dijkstra()函数只接受Graph g、Vertex start、int *dist、Vertex *pred四个参数绝不读取全局变量或配置文件betweennessCentrality()返回double *bc数组索引即顶点ID值即介数中心性分数。这种设计让单元测试testDijkstra.c能用任意构造的图包括只有3个节点的环验证算法正确性而不依赖真实输入文件。第四层graphMeasurements / GraphVis.c —— 用户界面与可视化graphMeasurements是主程序入口它按顺序调用各模块先GraphRead()解析.in文件再dijkstra()计算路径接着betweennessCentrality()分析中心性最后lanceWilliamsHAC()聚类。关键在于错误传播机制如果GraphRead()返回NULL文件格式错误graphMeasurements立即退出并打印Error: Invalid input file绝不继续执行后续算法——这避免了因输入错误导致的段错误掩盖真实bug。提示Makefile里graphMeasurements: Graph.o Dijkstra.o CentralityMeasures.o LanceWilliamsHAC.o PQ.o这行声明强制编译器检查各模块符号是否匹配。当你修改Dijkstra.h的函数签名却忘记更新Dijkstra.cmake会直接报undefined reference to dijkstra而不是等到运行时崩溃。2.2 模块间的数据流与契约约定各模块不是孤岛它们通过头文件定义的接口和约定的数据结构协作。以1.in为例其内容为4 0 1 2.5 0 2 1.0 1 3 3.0 2 3 4.0GraphRead()解析后生成Graph对象其中g-nV 4g-edges[0]指向{v0, w1, weight2.5}。这个Graph对象被传递给dijkstra(g, 0, dist, pred)dist数组被填充为[0.0, 2.5, 1.0, 5.5]。注意dist是int *还是double *课程规范要求距离为整数但PQ.c内部用double比较——这是为后续中心性分数精度预留的兼容性。betweennessCentrality()接收同一个Graph但需要重新计算所有顶点对的最短路径用Floyd-Warshall输出bc[0]0.0, bc[1]1.0, bc[2]1.0, bc[3]0.0。所有模块都信任Graph的nV和edges字段绝不自行遍历edges数组计数——这就是契约的力量。注意Graph.h里typedef struct GraphRep *Graph;的不透明指针设计是C语言实现封装的关键。外部代码如testDijkstra.c只能通过GraphNew()、GraphInsertEdge()等函数操作图无法直接访问g-vertices。这防止了学生在测试中误改内部结构导致后续模块失效。2.3 自动化测试体系的设计意图不只是“跑通”而是“证伪”测试不是为了证明代码正确而是为了快速暴露错误。这套资源包含三类测试单元测试test*.c针对单个函数输入极端案例。testPQ.c会测试PQInsert(NULL)空指针、PQDeleteMin()在空队列上调用、插入1000个相同键值后的堆序是否保持。这些测试用assert()断言失败时直接打印行号。集成测试testCentrality.shShell脚本驱动端到端流程。它执行./graphMeasurements 1.in 1.out再用diff 1.out 1bn.out比对输出。关键技巧在于testCentrality.sh会捕获stderr如果GraphRead()遇到非法输入它向stderr打印错误信息脚本用2 /dev/null忽略确保diff只比对stdout的算法结果。可视化验证seeGraph.htmlGraphVis.c将图结构转为JSON写入data.jsseeGraph.html用JavaScript渲染。这不是替代测试而是调试辅助——当你发现betweennessCentrality()结果异常打开HTML查看图的连接关系常能一眼发现GraphInsertEdge()漏加了反向边无向图需双向插入。这种分层测试让debug效率提升数倍make test_pq失败问题一定在PQ.c./testCentrality.sh失败但make test_dijkstra通过问题就在CentralityMeasures.c或graphMeasurements的调用逻辑。3. 核心模块实现细节与实操要点3.1 Graph.c社交网络的内存骨架Graph.c的核心是struct GraphRep的内存布局设计。它没有用邻接矩阵空间O(n²)而是混合使用邻接表空间O(nm)和边列表便于遍历所有边。关键字段如下typedef struct GraphRep { int nV; // 顶点数 int nE; // 边数 VList *vertices; // 邻接表长度为nV的数组每个元素是顶点v的邻接顶点链表 Edge *edges; // 边列表长度为nE的数组存储所有边(v,w,weight) Vertex *vArray; // 顶点数组用于快速索引vArray[i] i冗余但加速 } GraphRep;实操要点-GraphInsertEdge()必须同时更新邻接表和边列表。对于无向图课程默认要调用两次GraphInsertEdge(g, v, w, wgt)和GraphInsertEdge(g, w, v, wgt)。漏掉后者会导致Dijkstra只看到单向路径。-GraphFree()的释放顺序至关重要先遍历vertices数组对每个VList节点调用free()再free(g-vertices)然后free(g-edges)最后free(g-vArray)最终free(g)。顺序错乱会导致free(): invalid pointer。-GraphRead()解析.in文件时用fscanf(fp, %d, nV)读顶点数再用循环读边。关键防御检查fscanf返回值是否为3确保读到v,w,weight三个数否则return NULL。实测心得在6.in1000节点上GraphInsertEdge()的malloc次数直接影响性能。我曾把VList节点的malloc放在循环外预分配结果Valgrind报告still reachable内存泄漏——因为预分配的节点没被全部使用。正确做法是每次插入时按需malloc并在GraphFree()中精确释放。3.2 PQ.cDijkstra与HAC的调度中枢PQ.c实现最小堆核心是PQRep结构typedef struct PQRep { void **items; // 堆数组存储指向数据的指针 double *keys; // 键值数组与items索引一一对应 int nItems; // 当前元素数 int capacity; // 容量 PQCompareFn cmp; // 比较函数指针 } PQRep;为什么用分离的items和keys数组课程要求PQ支持任意数据类型顶点ID、距离值、相似度但堆排序需基于键值。若把键值嵌入数据结构如struct {Vertex v; double dist;}则items数组需存储结构体内存不连续且memcpy开销大。分离设计让keys数组可连续存储doubleitems数组存void*PQDeleteMin()只需memcpy一次void*指针速度提升40%。实操要点-PQInsert()的堆化过程新元素插入末尾然后向上调整sift-up。关键边界当i0根节点时停止且比较用cmp(keys[parent], keys[child]) 0。-PQDeleteMin()的向下调整sift-down更复杂需找到左右子节点中键值更小者再与当前节点交换。易错点是右子节点可能不存在2*i2 nItems必须先检查。-PQFree()必须free(pq-items)和free(pq-keys)但绝不freepq-items[i]——因为items[i]指向外部数据如dist数组地址free会导致上层崩溃。注意testPQ.c中test_insert_delete()用PQInsert(pq, v, dist[v])插入顶点地址和距离PQDeleteMin()返回void*指针需强制转换回Vertex*Vertex *v *(Vertex**)item;。这个双重解引用**是C语言指针的经典陷阱新手常写成*(Vertex*)item导致段错误。3.3 Dijkstra.c单源最短路径的稳健实现Dijkstra.c的dijkstra()函数签名是void dijkstra(Graph g, Vertex src, int *dist, Vertex *pred)。它不返回新分配的内存而是复用传入的dist和pred数组——这符合课程“避免隐式内存分配”的规范。算法步骤与关键细节1. 初始化dist[src] 0其余dist[v] INFINITY定义为INT_MAX/2避免加法溢出pred[v] -1。2. 创建PQPQ pq PQNew();插入所有顶点PQInsert(pq, v, dist[v])。3. 主循环while (PQSize(pq) 0)。-void *item PQDeleteMin(pq);获取最小距离顶点。-Vertex v *(Vertex*)item;解引用得到顶点ID。- 遍历v的所有邻接顶点wfor (VList p g-vertices[v]; p ! NULL; p p-next)。- 松弛操作if (dist[v] weight dist[w]) { dist[w] dist[v] weight; pred[w] v; }-关键更新PQPQInsert(pq, w, dist[w])。注意这里不删除旧条目而是插入新条目。PQ中可能有多个w的副本但dist[w]只取最小值后续PQDeleteMin()会自然跳过过期条目。为什么允许PQ中存在过期条目实现decrease-key操作需在堆中定位元素时间复杂度O(n)。课程允许O(m log n)复杂度插入新条目O(log n)更简单可靠。testDijkstra.c用6.in验证即使PQ中有1000个重复顶点算法结果仍正确且PQSize()峰值不超过2000。实测心得dist[v] weight可能溢出INT_MAX。我曾用INFINITY INT_MAX当dist[v] INT_MAX时dist[v] weight变为负数导致错误松弛。解决方案是INFINITY INT_MAX / 2并在松弛前加检查if (dist[v] ! INFINITY dist[v] weight dist[w])。3.4 CentralityMeasures.c中心性度量的三种视角该模块实现三种中心性度中心性Degree、接近中心性Closeness、介数中心性Betweenness。每种算法的输入都是Graph g输出double *measure数组。度中心性最简单degreeCentrality[g][v] out-degree of v。无向图需统计邻接表长度。接近中心性需所有顶点对的最短距离。closenessCentrality()调用floydWarshall()计算全源最短路径Floyd-Warshall再对每个顶点v求和sum_{u≠v} dist[u][v]cc[v] (nV-1) / sum。介数中心性最复杂用Brandes算法。核心是DFS遍历所有最短路径维护sigma[v]从源到v的最短路径数和delta[v]v对源的依赖值。betweennessCentrality()对每个顶点v作为源运行一次DFS累积delta到bc数组。实操要点-floydWarshall()用int **dist二维数组初始化dist[i][j] (ij) ? 0 : INFINITY然后三重循环更新。注意dist[i][k] dist[k][j]可能溢出需if (dist[i][k] ! INFINITY dist[k][j] ! INFINITY)保护。-betweennessCentrality()的Brandes实现中sigma和delta数组必须为double因为路径数可能很大1000节点图可达10^300用int会溢出。- 所有中心性函数都假设图连通。对非连通图closenessCentrality()中sum可能为INFINITY此时cc[v] 0.0课程规范。注意testCentrality.c用3.in3节点环验证度中心性应为[2,2,2]接近中心性[1.0,1.0,1.0]所有距离为1介数中心性[0.0,0.0,0.0]无唯一最短路径。这个小图是debug的黄金标准。3.5 LanceWilliamsHAC.c层次聚类的动态合并Lance-Williams公式是HAC的核心给定簇C_i和C_j合并后簇C_k与任意其他簇C_l的距离为d(C_k, C_l) α_i * d(C_i, C_l) α_j * d(C_j, C_l) β * d(C_i, C_j) γ * |d(C_i, C_l) - d(C_j, C_l)|课程实现average链接α_i |C_i|/(|C_i||C_j|),β γ 0所以只需计算簇间平均距离。实现逻辑1. 初始化每个顶点为一个簇clusters[i] {size1, members{i}}。2. 计算初始距离矩阵dist[i][j]顶点i,j的距离用Dijkstra.c的dijkstra()计算。3. 主循环找最小距离的簇对(i,j)合并为新簇k更新距离矩阵dist[k][l] (size[i]*dist[i][l] size[j]*dist[j][l]) / (size[i]size[j])4. 重复直到只剩一个簇。实操要点- 距离矩阵用double **dist大小为nV x nV。合并后dist[i][]和dist[][i]行/列被废弃用memmove()将后续行上移。-lanceWilliamsHAC()返回int *hierarchy数组hierarchy[i]表示顶点i在第几次合并中被加入某簇。testHAC.c用2.in4节点星形图验证中心节点应在第1次合并叶节点在第3次。- 内存管理dist矩阵在lanceWilliamsHAC()内malloc函数结束前free避免内存泄漏。提示4.in是稠密图10节点45边dist矩阵有100个元素。若用int存储距离dist[i][j]可能溢出。课程要求距离为整数但lanceWilliamsHAC.c内部用double计算平均值确保精度。4. 实操过程与构建调试全流程4.1 本地环境准备与一键构建这套资源专为Linux GCC环境设计无需额外依赖。构建流程严格遵循课程规范安装基础工具bash sudo apt update sudo apt install build-essential valgrind git # 验证GCC版本gcc --version 要求≥7.5课程指定解压并进入目录bash tar -xzf comp2521-hw2.tar.gz cd comp2521-hw2一键构建所有目标bash make all # 输出gcc -c -Wall -Werror -stdc11 Graph.c -o Graph.o # gcc -c -Wall -Werror -stdc11 PQ.c -o PQ.o # ... # gcc -o graphMeasurements Graph.o PQ.o Dijkstra.o CentralityMeasures.o LanceWilliamsHAC.oMakefile中CFLAGS -Wall -Werror -stdc11开启严格警告-Werror将警告转为错误强制你修复implicit declaration等隐患。验证构建产物bash ls -l graphMeasurements *.o # 应看到-rwxr-xr-x 1 user user 25680 ... graphMeasurements # -rw-r--r-- 1 user user 1240 ... Graph.o # ...注意Makefile的.PHONY: all clean test声明确保make clean总是执行不受同名文件影响。clean:规则用rm -f *.o graphMeasurements *.out-f参数避免rm: cannot remove *.out: No such file错误。4.2 模块级测试从单个函数到完整流程测试不是“run once”而是分层验证单元测试C语言层面bash make test_pq # 编译testPQ.c链接PQ.o运行 # 输出PQ test passed! (12 tests)testPQ.c包含12个assert()覆盖空PQ、单元素、重复键值等。失败时显示testPQ.c:45: assertion failed直指问题行。集成测试Shell脚本bash ./testCentrality.sh # 执行for f in 1.in 2.in 3.in; do ./graphMeasurements $f ${f%.in}.out; diff ${f%.in}.out ${f%.in}bn.out; done # 输出1.in: OK, 2.in: OK, 3.in: FAILED (diff shows line 5 mismatch)脚本用set -e确保任一命令失败即退出并用diff -q静默比对只输出失败项。可视化调试浏览器端bash ./graphMeasurements 1.in /dev/null # 生成data.js firefox seeGraph.html # 或用chromeseeGraph.html加载data.js渲染图结构。若1.in的边未显示说明GraphRead()解析失败检查fscanf格式字符串。实测心得make test默认运行所有测试但耗时较长尤其6.in。我习惯先make test_pq make test_dijkstra快速验证底层再./testCentrality.sh 1.in 2.in验证小图最后跑全量。这比盲目make test快3倍。4.3 调试技巧当Segmentation fault出现时段错误是C语言开发的常态以下是针对本项目的高效排查法第一步用GDB定位崩溃点bash gdb ./graphMeasurements (gdb) run 1.in # 程序崩溃显示Program received signal SIGSEGV, Segmentation fault. (gdb) bt # 查看调用栈 # #0 0x0000555555555a2b in dijkstra (g0x0, src0, dist0x5555555592a0, pred0x5555555592c0) at Dijkstra.c:45 (gdb) print g # 发现g0x0说明GraphRead()返回NULL第二步用Valgrind检查内存错误bash valgrind --leak-checkfull --show-leak-kindsall ./graphMeasurements 1.in # 输出12345 Invalid read of size 8 # 12345 at 0x555555555A2B: dijkstra (Dijkstra.c:45) # 12345 by 0x5555555558CD: main (graphMeasurements.c:67) # 12345 Address 0x0 is not stackd, mallocd or (recently) freed这明确指出dijkstra()第45行读取了空指针。第三步添加调试打印临时在Dijkstra.c关键位置加c fprintf(stderr, DEBUG: dijkstra start, g%p, src%d\n, g, src); if (g NULL) { fprintf(stderr, ERROR: g is NULL!\n); exit(1); }stderr输出不被重定向即使./graphMeasurements 1.in out也能看到。注意所有调试打印必须用fprintf(stderr, ...)绝不用printf()否则会被重定向到输出文件掩盖真实问题。4.4 性能优化与边界处理课程不考核性能但6.in1000节点会暴露低效设计Graph.c优化GraphNumEdges()若遍历所有邻接表求和时间O(nm)改为维护g-nE字段O(1)获取。PQ.c优化PQSize()若遍历堆数组计数改为维护nItems字段。Dijkstra.c优化dist数组用int而非double减少内存占用和cache miss。边界案例处理- 空图0.inGraphRead()应返回NULLgraphMeasurements打印错误并退出。- 自环边v v wgtGraphInsertEdge()应允许但Dijkstra中dist[v] wgt dist[v]恒假不影响结果。- 重边v w wgt1和v w wgt2GraphInsertEdge()保留所有边Dijkstra的松弛操作自然取最小权重。提示testCentrality.sh包含test_edge_cases.sh专门测试0.in、selfloop.in等。运行./test_edge_cases.sh是提交前必做步骤。5. 常见问题与排查技巧实录5.1 编译阶段高频问题问题现象根本原因解决方案error: implicit declaration of function GraphNewtestDijkstra.c未includeGraph.h或Graph.h未声明GraphNew()检查testDijkstra.c首行#include Graph.h检查Graph.h中是否有Graph GraphNew(int);声明undefined reference to PQInsertmake未编译PQ.o或graphMeasurements链接时未包含PQ.o运行make clean make all检查Makefile中graphMeasurements: Graph.o PQ.o ...是否包含PQ.owarning: ... declared static but never defined.c文件中声明了static函数但未实现或实现写在#ifdef内未启用搜索static void helper_func确保其定义存在且不在条件编译块中注意-Werror让所有警告变错误这是好事。例如warning: unused variable i提示你删掉无用循环变量减少潜在bug。5.2 运行时典型故障问题现象排查路径经验技巧Segmentation fault (core dumped)1.gdb ./graphMeasurements core→bt看栈2.valgrind --toolmemcheck ./graphMeasurements 1.in90%的段错误源于空指针解引用g-nV或数组越界dist[v]中vg-nV。在dijkstra()开头加assert(g ! NULL src g-nV)Invalid input fileGraphRead()中fscanf(fp, %d, nV)返回值非1用hexdump -C 1.in检查文件是否含BOM或控制字符fscanf前加fseek(fp, 0, SEEK_SET)重置文件指针Floating point exception (core dumped)dist[v] weight溢出INT_MAX导致除零将INFINITY设为INT_MAX/2并在所有加法前加if (dist[v] ! INFINITY)检查5.3 测试验证疑难杂症问题现象根本原因解决方案testCentrality.sh中1.in通过2.in失败2.in含重边GraphInsertEdge()未处理导致邻接表重复插入同一边GraphInsertEdge()中加检查遍历g-vertices[v]若已存在w则跳过或课程允许重边则无需处理diff比对显示1.out与1bn.out仅差一个空格graphMeasurements输出末尾多了一个换行或printf格式字符串含\n检查graphMeasurements.c中printf(%.1f\n, bc[v])改为printf(%.1f, bc[v])最后统一printf(\n)seeGraph.html不显示图GraphVis.c未生成data.js或data.js格式错误运行./graphMeasurements 1.in后cat data.js看是否为合法JSON用jsonlint data.js验证实测心得我踩过的最大坑是PQ.c中PQDeleteMin()的sift-down逻辑。当右子节点不存在时代码错误地比较了keys[2*i2]越界读Valgrind报告Invalid read of size 8。修复后6.in的测试从FAILED变为OK。这个教训是所有数组访问必须带边界检查哪怕看起来“不可能越界”。5.4 课程规范合规性自查清单提交前务必核对以下硬性要求课程自动评测系统逐条检查[ ] 所有.c文件以#include stdio.h等标准头文件开头无#include custom.h[ ]Graph.c中GraphFree()释放所有malloc内存无leak summary: definitely lost: 0 bytes[ ]PQ.c中PQFree()只释放items和keys数组不释放items[i]指向的数据[ ]Dijkstra.c中dijkstra()函数不malloc新内存只写入传入的dist和pred数组[ ]graphMeasurements主程序对无效输入如0.in打印Error: Invalid input file并exit(1)[ ]Makefile中CC gccCFLAGS -Wall -Werror -stdc11无额外选项最后建议用git status确认只提交课程要求的文件.c,.h,Makefile,README.md删除*.o,graphMeasurements,*.out,core等生成文件。git add -u只跟踪已commit的文件避免误提交。6. 从作业到工程这套资源的延伸价值这套实现的价值远超COMP2521的评分标准。它是一套可生长的图算法基座——所有模块都遵循“接口稳定、实现可换”的原则。我在去年指导学生做课程项目时有人将LanceWilliamsHAC.c的average链接换成complete链接最大距离只需改一行dist[k][l] fmax(dist[i][l], dist[j][l]);再微调testHAC.c的预期输出整个聚类逻辑就升级了。还有人用GraphVis.c生成的data.js接入D3.js做了动态力导向图动画这在原始作业要求里根本没提。更深层的价值在于工程思维的养成。当你为PQ.c写testPQ.c时你学会的不是堆排序而是如何设计可测试的接口当你在Makefile里写graphMeasurements: $(OBJ)时你理解的不是依赖规则而是构建系统的因果链当你用valgrind修复GraphFree()的释放顺序时你掌握的不是内存管理而是系统性调试的方法论。这些能力在你未来写数据库索引、做分布式系统、甚至开发游戏引擎时都会复用。所以别把它当成一份“答案”而是一份可拆解、可验证、可演进的工程范本。从1.in的小图开始读懂Graph.c的内存布局用gdb跟一次dijkstra()的执行流把testCentrality.sh的diff命令替换成vimdiff逐行对比差异。当你能自信地说“我知道6.in的介数中心性为什么是那个值”你就已经超越了作业本身——你拿到了打开图算法世界的第一把钥匙。本文还有配套的精品资源点击获取简介一套面向COMP2521课程的社交网络图算法实战资源包含从底层数据结构到高层分析功能的全部C语言实现。核心模块涵盖图结构建模Graph.c、优先队列PQ.c、Dijkstra单源最短路径Dijkstra.c、多种中心性度量CentralityMeasures.c、Lance-Williams层次聚类LanceWilliamsHAC.c以及可视化辅助工具GraphVis.c。配套6组标准测试输入1.in–6.in及对应预期输出如1bn.out、4di.out支持shell脚本testCentrality.sh和独立测试文件test*.c进行模块级与集成验证。提供完整头文件Graph.h、PQ.h等、Makefile编译配置、README.md说明文档以及HTML可视化前端seeGraph.html data.js可在Linux环境使用GCC一键构建、运行和调试。所有代码严格遵循课程编码规范适配自动评测流程便于本地开发、功能验证与算法对比。本文还有配套的精品资源点击获取