Floyd算法实战:C语言解“哈利·波特的考试”图论问题
1. 题目解析与核心需求拆解“哈利·波特的考试”这道题是很多C语言学习者和算法初学者在练习数据结构尤其是图论时会遇到的一道经典题目。我第一次看到这个标题时也以为会有什么魔法咒语或者复杂的剧情逻辑但实际上它考察的是一个非常纯粹的图论算法问题——多源最短路径。题目背景通常是这样霍格沃茨有N门魔法课每门课可以看作一个顶点课程之间的转换难度或者说是“魔法值”构成了带权有向图的边。哈利·波特需要找到一门“最难”的课这里的“最难”不是指课程本身内容而是指从这门课出发到其他所有课程中最难到达的那门课所需要的“魔法值”最大。听起来有点绕别急我们换个说法。这本质上是一个单源最短路问题的集合。对于每一门课即每一个顶点我们都需要计算它到其他所有顶点的最短距离。然后对于这门课我们找出这些最短距离中的最大值即从这门课出发到最难到达的课程的距离。最后我们比较所有课程的这些“最难到达距离”找出其中最小的那个以及对应的课程。如果存在多门课满足条件则输出编号最小的。如果存在某门课无法到达其他所有课即图不连通则输出0。所以核心算法需求非常明确建立图模型将课程和转换难度建模为带权有向图。计算任意两点间最短路径这是算法的核心。由于需要计算所有顶点对之间的最短路径使用 Floyd-Warshall 算法是最直接的选择。它的时间复杂度是 O(N³)对于题目常见的 N ≤ 100 的数据范围是完全可行的。后处理与结果判定对每个顶点 i找出dist[i][1...N]中的最大值maxDist[i]即从i出发到其他点的最远距离。然后在所有maxDist[i]中找出最小值minOfMaxDist。如果某个maxDist[i]为无穷大表示有不可达的顶点则整体结果无效。理解了需求我们再来看看实现中的几个关键点这也是新手最容易栽跟头的地方。2. 图的数据结构与Floyd算法实现精讲2.1 邻接矩阵的初始化无穷大与自环在C语言中我们通常用一个二维数组dist[N1][N1]来表示邻接矩阵并直接作为Floyd算法的距离矩阵。初始化是第一步也是决定算法正确性的基石。#define MAX_V 105 // 假设最大顶点数略大于题目范围 #define INF 0x3f3f3f3f // 一个常用的“无穷大”值 int dist[MAX_V][MAX_V]; int N, M; // N顶点数M边数 void initGraph() { // 1. 自己到自己的距离为0 for (int i 1; i N; i) { for (int j 1; j N; j) { if (i j) { dist[i][j] 0; } else { dist[i][j] INF; // 初始化为无穷大表示不可达 } } } // 2. 读入边信息 // 假设输入格式为顶点a, 顶点b, 权值w for (int k 0; k M; k) { int a, b, w; scanf(%d %d %d, a, b, w); dist[a][b] w; // 有向图 // 如果题目是无向图则需要加上 dist[b][a] w; } }这里有几个极易出错的细节INF的选择为什么是0x3f3f3f3f这个值约等于10^9在int范围内且两个这样的值相加不会溢出0x3f3f3f3f * 2 0x7fffffff。如果你用INT_MAX或0x7fffffff在松弛操作dist[i][k] dist[k][j]时一旦dist[i][k]为最大值相加就会导致整数溢出变成负数从而影响算法结果。0x3f3f3f3f是一个安全且方便 memset 初始化的值memset(dist, 0x3f, sizeof(dist))会将所有字节设为0x3f对于int数组每个int就变成了0x3f3f3f3f。自环距离为0dist[i][i] 0必须显式设置。这是Floyd算法正确工作的前提表示从自己到自己的最短距离是0。虽然逻辑上显而易见但忘记初始化会导致后续判断出错。重边处理题目通常会说“题目保证输入数据是有效的”但有些题目可能存在重边即同一对顶点有多条边。这时需要取最小值dist[a][b] min(dist[a][b], w)。养成这个习惯能避免很多隐蔽的bug。2.2 Floyd-Warshall算法的三重循环顺序与逻辑Floyd算法的核心代码非常简短但内涵深刻。void floyd() { for (int k 1; k N; k) { // 中间点 for (int i 1; i N; i) { for (int j 1; j N; j) { // 关键防止INF相加溢出必须先判断 if (dist[i][k] INF dist[k][j] INF) { if (dist[i][j] dist[i][k] dist[k][j]) { dist[i][j] dist[i][k] dist[k][j]; } } } } } }为什么k循环必须放在最外层这是理解Floyd算法的关键。算法的思想是动态规划dist[k][i][j]表示只允许使用前k个顶点作为中间点从i到j的最短路径长度。当k从1遍历到N时我们逐渐放宽“可使用的中间点”的范围最终得到任意两点间的最短路径。将状态压缩到二维数组后就要求k这层循环必须在外层以保证在计算dist[i][j]时dist[i][k]和dist[k][j]已经是考虑了前k-1个中间点的最优解。如果顺序错了结果就不正确。关于INF的判断这是实现中的另一个关键点。如果dist[i][k]或dist[k][j]是INF那么它们的和是无意义的直接比较可能会因为INF被定义为一个具体的大数而产生错误更新。因此必须先判断两者都不是INF再进行松弛操作。有些简单的实现省略了这个判断在INF选择得当且题目数据保证不会出现INF某值的情况下可能也能通过但加上判断是更严谨、更安全的做法。3. 结果计算与边界条件处理Floyd算法跑完后dist矩阵就存储了所有点对之间的最短距离。接下来就需要根据题目要求找出哈利·波特应该参加的那门考试课程。int findExamCourse() { int minOfMaxDist INF; // 记录所有“最难距离”中的最小值 int examCourse 0; // 对应的课程编号 for (int i 1; i N; i) { int maxDistForI 0; // 从课程i出发到其他课程的最远最短距离 // 遍历所有其他课程j for (int j 1; j N; j) { if (dist[i][j] maxDistForI) { maxDistForI dist[i][j]; } } // 关键判断如果从i出发存在不可达的课程则maxDistForI会是INF if (maxDistForI INF) { // 这门课无法作为起点因为它不能到达所有其他课 // 根据题目要求一旦出现这种情况整个结果就是0 // 但我们需要遍历完所有课程确认吗其实找到第一个就可以返回0了。 // 更严谨的做法记录下这个情况但继续循环因为题目要求输出编号最小的可行课程。 // 实际上如果有一门课不可达那么“所有最难距离中的最小值”这个集合就不完整最终结果应为0。 // 我们可以在循环外用一个标志位记录。 } // 如果这门课可以到达所有其他课则用它的最难距离去更新全局最小值 if (maxDistForI minOfMaxDist) { minOfMaxDist maxDistForI; examCourse i; } } // 后处理检查是否所有课程都能作为起点即图是强连通的不题目要求是每个顶点都能到其他所有顶点这是“竞赛图”的连通性 // 实际上题目要求的是如果存在至少一门课从它出发可以到达其他所有课那么就在这些课里找“最难距离”最小的。 // 如果不存在这样的课即对于每个顶点i都存在另一个顶点j使得dist[i][j]INF则输出0。 // 更准确的判断逻辑 int canFind 0; int globalMinMax INF; int ansId 0; for (int i 1; i N; i) { int maxDist -1; int isConnected 1; // 假设i能到所有点 for (int j 1; j N; j) { if (dist[i][j] maxDist) { maxDist dist[i][j]; } if (dist[i][j] INF) { isConnected 0; // i无法到达j break; // 已经确定i无效内层循环可以提前结束 } } if (isConnected) { // i可以到达所有顶点 canFind 1; if (maxDist globalMinMax) { globalMinMax maxDist; ansId i; } } } if (canFind) { // 输出 ansId 和 globalMinMax printf(%d %d\n, ansId, globalMinMax); } else { printf(0\n); } return 0; }这部分代码的逻辑比看起来要复杂主要体现在连通性判断上。题目真正的意思是在那些能够到达其他所有顶点的顶点中找一个“最难距离”即到其他点的最短距离的最大值最小的。如果不存在这样的顶点即对于图中每一个顶点至少存在一个它无法到达的顶点则输出0。常见的错误理解认为需要整个图是强连通图任意两点互相可达。其实不需要只需要存在一个顶点它能到达其他所有点即可。这个顶点就像是一个“源点”。在计算maxDistForI时如果遇到INF不能简单地将其当作一个巨大值参与比较。因为INF意味着不可达这门课本身就应该被排除在候选之外。所以必须在计算maxDistForI的过程中一旦发现INF就标记该顶点无效。输出要求如果找到输出编号最小的那个课程及其对应的“最难距离”。所以我们在更新globalMinMax时判断条件是maxDist globalMinMax而不是。这样当距离相等时不会更新ansId从而保证了编号小的优先因为我们是按编号从小到大遍历的。4. 完整代码实现与测试用例分析将以上所有部分组合起来并加上输入输出就得到了完整的解决方案。下面是一个整合后的代码框架并附上详细的注释。#include stdio.h #include string.h #define MAX_V 105 #define INF 0x3f3f3f3f int dist[MAX_V][MAX_V]; int N, M; void initGraph() { // 使用memset初始化所有距离为INF非常高效 memset(dist, 0x3f, sizeof(dist)); for (int i 1; i N; i) { dist[i][i] 0; // 自环为0 } for (int i 0; i M; i) { int a, b, w; scanf(%d %d %d, a, b, w); // 处理可能的重复边取最小值 if (w dist[a][b]) { dist[a][b] w; } } } void floyd() { for (int k 1; k N; k) { for (int i 1; i N; i) { // 一个小优化如果i到k不可达则跳过对j的循环 if (dist[i][k] INF) continue; for (int j 1; j N; j) { // 防止INF相加溢出 if (dist[k][j] INF) continue; int newDist dist[i][k] dist[k][j]; if (dist[i][j] newDist) { dist[i][j] newDist; } } } } } int main() { scanf(%d %d, N, M); initGraph(); floyd(); int candidateId 0; int globalMinMaxDist INF; // 遍历每一门课寻找符合条件的“源点” for (int i 1; i N; i) { int maxDist -1; int isValid 1; // 标记顶点i是否可达所有其他顶点 for (int j 1; j N; j) { if (dist[i][j] maxDist) { maxDist dist[i][j]; } // 如果发现不可达的点则顶点i无效 if (dist[i][j] INF) { isValid 0; break; // 提前结束内层循环 } } // 如果顶点i有效且它的“最难距离”是目前最小的 if (isValid maxDist globalMinMaxDist) { globalMinMaxDist maxDist; candidateId i; } } if (candidateId 0) { // 没有找到任何一个顶点能到达所有其他顶点 printf(0\n); } else { printf(%d %d\n, candidateId, globalMinMaxDist); } return 0; }现在我们来设计几个测试用例验证代码的正确性。测试用例1基础连通案例输入 6 11 3 4 70 1 2 1 5 4 50 2 6 50 5 6 60 1 3 70 4 6 60 3 6 80 5 1 100 2 4 60 5 2 80这是一个经典测试图需要手动计算或信任已知结果预期输出应该是2 60。顶点2到其他点的最短距离最大值是60到顶点4在所有顶点中这个值最小。测试用例2存在不可达顶点输入 3 2 1 2 10 2 3 20顶点1无法到达顶点3dist[1][3] INF。顶点2可以到达1和3。顶点3无法到达1。因此只有顶点2是有效的“源点”。它的maxDist是max(20, 0) 20到顶点3的距离是20到自己的距离是0。输出应为2 20。测试用例3全不连通输入 4 0没有边。对于任何一个顶点i到其他任意j (j!i)的距离都是INF。因此不存在有效的“源点”。输出应为0。测试用例4多个候选取编号最小输入 4 3 1 2 5 1 3 5 1 4 5只有顶点1可以到达2、3、4。顶点2、3、4都无法到达其他非自身的顶点。所以唯一有效的是顶点1它的maxDist是5。输出1 5。如果图是对称的无向边那么每个顶点都能到达其他点且maxDist都相等此时应输出编号最小的顶点1。通过这些测试可以全面检查算法的连通性判断、最值比较和边界处理是否正确。在你自己编写时务必用这些案例测试一下这是调试和确保代码鲁棒性的好习惯。

相关新闻

Flutter+OpenHarmony开发逆向思维训练App实践

Flutter+OpenHarmony开发逆向思维训练App实践

1. 逆向思维训练App的跨界开发背景在移动应用开发领域,Flutter因其跨平台特性已成为主流选择之一,而OpenHarmony作为新兴操作系统也正吸引着越来越多开发者的目光。这次我们要开发的是一款结合逆向思维训练和学习日历功能的复合型应用,技术栈…

2026/10/17 8:15:53 阅读更多 →
windows 驱动实例分析系列: wintun驱动分析-example篇(上)

windows 驱动实例分析系列: wintun驱动分析-example篇(上)

Wintun Example 示例程序深度解析 这部分作为演示如何使用wintun驱动进行二次开发的例子,分为两部分。 一、模块概述 example 文件夹是 Wintun 项目提供的一个完整的、可直接运行的示例程序,用于演示如何使用 wintun.dll 的 API 来创建虚拟网络适配器、配…

2026/10/18 15:28:05 阅读更多 →
北邻京网站茵建设:小站深耕长尾流量的逆袭之路

北邻京网站茵建设:小站深耕长尾流量的逆袭之路

在这个流量为王、大V横行的互联网时代,很多人一听到“建站”这两个字,脑子里蹦出来的全是高大上的电商平台、炫酷的3A级门户或者动辄百万预算的定制化开发。大家总觉得,只有那些拥有庞大服务器集群、顶尖UI设计团队和海量资金支撑的项目,才配叫“做网站”。然而,现实往往是…

2026/10/16 18:58:55 阅读更多 →

最新新闻

MATLAB数学实验大作业避坑指南:数值计算与代码验证技巧

MATLAB数学实验大作业避坑指南:数值计算与代码验证技巧

简介:MATLAB数学实验课程的练习题与大作业答案,可作为南邮计算机专业学生及MATLAB初学者的配套参考,用于完成实验报告、考前复习或命令自查。压缩包共1个docx文档,大小约635KB,打开即可看到按题号组织的完整解答。内容…

2026/10/18 15:28:03 阅读更多 →
163MusicLyrics 上手攻略:5 步搞定网易云与 QQ 音乐歌词批量保存

163MusicLyrics 上手攻略:5 步搞定网易云与 QQ 音乐歌词批量保存

163MusicLyrics 上手攻略:5 步搞定网易云与 QQ 音乐歌词批量保存 【免费下载链接】163MusicLyrics 云音乐歌词获取处理工具【网易云、QQ音乐】 项目地址: https://gitcode.com/GitHub_Trending/16/163MusicLyrics 163MusicLyrics 是一款跨平台的开源音乐歌词…

2026/10/18 15:28:03 阅读更多 →
AIStore免费AI公开课-智能体篇(十):Claude Code接入TaoToken,用Claude改整个代码库,开箱即用

AIStore免费AI公开课-智能体篇(十):Claude Code接入TaoToken,用Claude改整个代码库,开箱即用

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

2026/10/18 15:28:03 阅读更多 →
KV Cache上下文契约:一次批量推理注意力掩码错误的深度排查

KV Cache上下文契约:一次批量推理注意力掩码错误的深度排查

某个自研推理项目的调试过程,让我在一种极其隐蔽的 KV Cache 问题上耗了整整一天。事情背景并不复杂:为了提升吞吐,我把批处理推理里的缓存从"每请求独立申请"改成"共享显存池复用",顺带为适配某个优化 kerne…

2026/10/18 15:28:03 阅读更多 →
AI内容创作变现真相:合规生产与长期资产如何平衡

AI内容创作变现真相:合规生产与长期资产如何平衡

最近圈子里都在转一条消息:某个靠情感话题起号、以各种“恋爱技巧”和“人设剧本”收割流量的头部账号,被平台全线封杀,连小号、矩阵号都没放过。传播的截图里常配一句话:这人早就不靠内容吃饭了,切了AI工具&#xff0…

2026/10/18 15:28:03 阅读更多 →
小程序时间戳与日期转换全攻略:避开iOS时区与格式化大坑

小程序时间戳与日期转换全攻略:避开iOS时区与格式化大坑

不少开发者第一次在小程序里处理日期和时间戳,都会觉得这是个"小功能",随手搜个方法就完事了。但真到了上线前,各种怪异问题就冒出来了:时间差了8小时、iOS上日期解析直接报错、凌晨零点生成的订单总是显示昨天……我自…

2026/10/18 15:27:02 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 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/18 0:55:02 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →