资讯动态

三角形分类判定:排序、平方比较与等腰等边输出

发布时间:2026/10/27 3:47:56 来源:尧图企业网站定制
三角形分类这道题摆在新手面前时往往第一反应是这有什么难的不就是比较三个数吗真正动手写一半以上的人第一次提交都拿不到全对。它把三个最容易翻车的点塞进了二十来行代码里判定条件要不要排序、角的类型用不用开根号、等腰和等边到底是并列还是互斥。这三个问题任何一个想岔了答案就会在各种刁钻数据上崩掉。这篇文章不打算只丢一段能过的代码给你而是把每一步背后的数学依据、工程取舍、以及我自己在这道题上踩过的坑完整拆开从三根木棍怎么才能围成三角形一直讲到交上去之前该造哪些数据自测。适合刚接触判定类题目、或者以前写过但总是死在某个隐藏测试点上的人也适合想拿它当例题给别人讲清楚为什么代码要这么写的读者。1. 三根木棍能不能围成三角形判定条件的来龙去脉1.1 从任意两边之和大于第三边到只验最长边课本上给出的三角形三边关系是任意两边之和大于第三边。三个不等式写出来就是 abc、acb、bca全部满足才能构成三角形。直接照抄成代码就是三个条件用逻辑与连起来这样写当然能过但它有一个隐藏的认知负担——你得同时维护三个变量调试的时候一旦出错还要判断是哪一条不成立。可以换个角度想如果三条边里最长的那条是 c那另外两条加起来能不能超过 c才是唯一可能出问题的地方。因为 a 和 c 是较小或相等的量acb 这个不等式里b 是三者中最小的ac 必然大于 b除非有 0 或负数而边长不会。同理 bca。所以三个不等式里只有两个较小的数之和大于最大的数这一个有可能不成立另外两个是恒成立的多余条件。这个推论的价值不在于省几行代码而在于它把问题从三个条件同时成立简化成了一个条件是否成立。一旦你把它想通了后面所有的操作都能围绕排序后的数组展开先让 a[0]≤a[1]≤a[2]然后整个判定过程就只跟这三个位置的相对大小打交道逻辑链一下子短了。1.2 等号这一分界线退化三角形是怎么被排除的真正容易翻车的是等号。a[0]a[1] a[2] 和 a[0]a[1] a[2] 看起来只差一个字符结果却是天壤之别。当两边之和恰好等于第三边时三根木棍是能首尾相接摆成一条直线的但那不叫三角形数学上叫退化三角形。比如 1、2、3 这组数据摆出来的图形是一条长 3 的线段上落着两个接头面积为 0。所以判定条件必须是严格大于。写代码的时候我习惯反过来写不合法的情况if (a[0] a[1] a[2])直接输出不能构成然后提前返回。这种先排除非法、再处理合法的写法比嵌套在 if 里面能少一层缩进后面判断角类型的代码全部位于同一层级读起来清爽很多。实测下来这道题最常见的一个 WA 就是把写成了导致 1 2 3、2 3 5 这类退化数据被判成了合法三角形然后程序继续往下走又因为平方关系把它判成了直角或钝角最终输出了一堆本不该出现的行。这类错误在本地用手动输入的小数据上极难发现因为人脑会下意识地只测像三角形的数据。1.3 先排序再判断省掉的不只是三行代码排序这一步很多人觉得可有可无反正三个数比较一下也能找到最大值。但排序带来的收益是结构性的它让谁是最长边这个问题永久性地被解决了后续不管是判断能否成三角形还是用勾股定理判断角类型都可以直接写 a[0]、a[1]、a[2]不用再关心用户输入的顺序。假设输入是 5 3 4不排序的话你得先找出最大值在哪或者干脆写三组平方和比较来判断直角a²b²c² 三种排列都要试一遍。而排序之后5 3 4 变成 3 4 5斜边固定是 a[2]判断直角只需要一次a[0]*a[0] a[1]*a[1] a[2]*a[2]。代码量减少一半出错概率同步下降。从工程习惯上说这三个数的排序成本可以忽略三次比较换来的是整段逻辑的确定性。我在写任何涉及多条边、多个数、需要比较大小关系的判定题时第一反应都是先排序这不是偷懒是把不确定性尽早收敛掉。类比一下你要判断三个人里谁最高、谁和谁一样高先把他们按身高排成一队后面所有问题都变成了看相邻位置。2. 判断角的类型为什么能手算平方却不用开根号2.1 余弦定理、勾股定理与三种角的统一判据三角形按角分类有三种锐角、直角、钝角。判别依据来自余弦定理对于最长边 c 所对的角 C有 c² a² b² - 2ab·cos C。因为 a、b 都是正数所以 cos C 的符号完全由 c² 与 a²b² 的大小关系决定。当 c² a²b² 时cos C 0角 C 是 90 度这就是勾股定理三角形是直角三角形。当 c² a²b² 时cos C 0角 C 小于 90 度又因为 C 是最长边所对的角它本身就是最大的角最大角都小于 90 度其余两个自然更小于是整个三角形是锐角三角形。当 c² a²b² 时cos C 0最大角超过 90 度三角形是钝角三角形。这条判据的漂亮之处在于它把三种情况压缩成了一个数与另一个数的比较而且全程只需要平方不需要开方也不需要三角函数。很多新手第一反应是去算角度用 acos 求出来再和 90 度比那样不但慢还会引入浮点误差在边界数据上随机翻车。2.2 整数平方比较对比浮点运算的实际差距为什么一定要避开浮点举个具体的例子三条边是 1、1、√2 这种情况在整数输入里不会出现但 3、4、5 这种整数直角三角形如果你用sqrt或者acos去算得到的结果未必精确等于期望值。计算机用二进制表示小数0.1 都不能精确存储更别说开方出来的无理数了。一旦判等写成if (angle 90.0)某些数据就会因为差了 1e-16 而走进错误分支。用整数平方就完全没有这个问题。a[0]*a[0] a[1]*a[1] 和 a[2]*a[2] 都是精确的整数相等就是相等不等就是不等没有任何模糊地带。这也是判定类题目的一条通用经验能用整数运算表达的判断绝不引入浮点。距离比较、斜率判断、面积判断都是同一个道理。判断方式是否需要开方精度风险代码复杂度推荐度求角度后与 90 度比较需要高边界易错中需引入数学库不推荐开方后比较边长需要中无理数截断中不推荐直接比较平方和不需要无低一行搞定推荐2.3 排序后一次比较完成三选一有了前面排序的铺垫角类型的判断可以写成三行算 x a[0]²a[1]²算 z a[2]²然后比较。x 等于 z 输出直角x 大于 z 输出锐角x 小于 z 输出钝角。这里有个容易混淆的点x 大于 z 对应的是锐角不是钝角。因为 x 是两个短边的平方和z 是最长边的平方x 更大意味着最长边还不够长最大角没到 90 度所以是锐角。我见过不少人第一次写反把写成钝角然后在 3 4 5 这种数据上碰巧还能对因为相等那条分支没走错直到遇上 2 3 4 才暴露。把这三个分支写成 if / else if / else 的链式结构可以保证只输出其中一行。这一点在下一节会重点讲因为它是这道题第二大的坑。3. 输出规则里藏着的两个坑互斥与包含3.1 角类型三选一必须走 else 分支按角分类一个三角形只能是锐角、直角、钝角三者之一不可能同时是两种。这个数学事实必须体现在代码结构上三个判断之间要用 else 隔开而不能写成三个独立的 if。写成独立 if 的后果是什么假如数据是 3 4 5第一个 if 判断直角成立输出一行第二个 if 判断锐角条件x z不成立不输出第三个 if 判断钝角x z也不成立不输出。看起来碰巧没事。但如果你把条件写宽松了比如锐角写成x z那么 3 4 5 就会同时输出直角和锐角两行直接错。更隐蔽的情况在钝角数据上。假设某人的判断逻辑是锐角x z钝角x z那么所有直角三角形都会同时命中两条输出三行。所以最稳妥的写法就是互斥链先判等再判大剩下的全归小。数学上这叫穷举且不重叠代码上就是 if / else if / else。3.2 等边三角形同时也是等腰三角形这是这道题里真正让人掉坑的地方也是最值得拿出来讲的一个概念等腰三角形的定义是至少有两边相等注意是至少。等边三角形三边都相等当然满足至少有两边相等所以它同时也是等腰三角形。这意味着当输入是 3 3 3 时正确输出应该是三行锐角三角形、等腰三角形、等边三角形。等腰那一行不能因为已经判了等边就跳过。很多人下意识觉得等腰和等边是并列的两种分类于是在代码里写了if 等边 ... else if 等腰 ...结果等边数据只输出两行少了一行判题直接报错。正确的思路是把这两个判断完全独立开等腰的判断条件是存在两边相等等边的判断条件是三边都相等两者各自成立各自输出互不影响。用排序后的数组来表达等腰就是 a[0]a[1] 或者 a[1]a[2]等边是 a[0]a[2]因为排过序首尾相等必然三者相等。输入已排序等腰条件 a[0]a[1] 或 a[1]a[2]等边条件 a[0]a[2]实际输出行3 3 3成立成立等腰 等边3 3 4成立不成立仅等腰3 4 4成立不成立仅等腰3 4 5不成立不成立都不输出3.3 输出顺序、大小写和换行的细节题目对输出的顺序是有要求的先输出角类型三选一再输出等腰最后输出等边。这个顺序不能乱因为判题机是逐行比对的。等腰和等边都输出时等腰那行必须在前面。字符串的大小写也必须一字不差。这类题目通常给的是首字母大写加小写的格式比如 Right triangle、Isosceles triangle单词之间一个空格。有人图省事写成全小写或者把 triangle 拼错本地看着没问题交上去就是 WA。我的习惯是把题目给的输出样例直接复制到代码里当字符串常量绝不手打这样一个字母都不会错。换行方面每行输出完要换行包括最后一行。有些判题环境对末尾是否有换行比较宽容但也有些会因为少了换行符而报格式错误。统一用\n结尾最省心比 endl 快也不会有刷新缓冲的开销。还有一点当判断为不能构成三角形时只输出那一行然后整个程序就结束后面的角类型、等腰、等边一个都不能输出。这要求你在排除分支里直接 return或者用一个标志位把后面的代码全包起来。我倾向直接 return语义最清楚。4. 手把手写出可提交的代码C 与 Python 两版4.1 C 版本的逐行拆解先看完整代码然后逐段解释。#include iostream #include algorithm using namespace std; int main() { long long a[3]; cin a[0] a[1] a[2]; sort(a, a 3); if (a[0] a[1] a[2]) { cout Not triangle \n; return 0; } long long x a[0] * a[0] a[1] * a[1]; long long z a[2] * a[2]; if (x z) { cout Right triangle \n; } else if (x z) { cout Acute triangle \n; } else { cout Obtuse triangle \n; } if (a[0] a[1] || a[1] a[2]) { cout Isosceles triangle \n; } if (a[0] a[2]) { cout Equilateral triangle \n; } return 0; }用数组而不是三个独立变量是为了能直接调用sort。sort(a, a 3)这种写法是 C 里对原生数组排序的标准姿势左闭右开a 3指向第四个位置不存在所以实际排序的是下标 0、1、2 这三个元素。变量类型选long long而不是int这是防御性设计。虽然这道题的数据范围可能不大但边长一旦到了几十万平方就会突破 int 的表示上限约 21 亿两个平方相加更是翻倍。用 64 位整数即使边长到 10 的 9 次方平方和也只有 2×10^18仍在安全范围内。这个习惯值得养成代价只是几个字节。判断能否构成三角形的那一行用了直接把退化情况归到不合法里。注意这里比较的是a[0] a[1]和a[2]两者都是 long long不会有溢出。如果你用的是 int 且边长接近上限a[0] a[1]本身就可能溢出成负数那样判定结果就完全错了——这是另一个隐藏的雷后面会细说。角类型的三个分支用 if / else if / else 保证互斥等腰和等边用两个独立的 if 保证可以同时成立这一段的写法基本就是这道题的标准答案骨架。4.2 Python 版本的写法与差异Python 版本逻辑完全一致只是语法更短a list(map(int, input().split())) a.sort() if a[0] a[1] a[2]: print(Not triangle) else: x a[0] * a[0] a[1] * a[1] z a[2] * a[2] if x z: print(Right triangle) elif x z: print(Acute triangle) else: print(Obtuse triangle) if a[0] a[1] or a[1] a[2]: print(Isosceles triangle) if a[0] a[2]: print(Equilateral triangle)Python 的整数是任意精度的不存在溢出问题这一点对判定类题目格外友好你完全不用操心变量类型。a.sort()是原地排序排完之后下标 0 就是最小值。结构上有一处不得不做的调整Python 没有return 0提前退出主流程的写法除非封装成函数所以排除分支用 else 把后面的代码包起来了。如果输入格式是多行而不是一行三个数input().split()就要改成三次int(input())读题时务必看清输入样例的排布方式这类题经常在这上面做文章。另外Python 的print自带换行不需要手动加。注意不要在字符串里再写一个\n否则会输出空行格式直接错。4.3 一组能覆盖全部分支的测试用例写完代码不能只测样例样例往往只覆盖一两个分支。按分支设计数据才是稳妥做法下面这组我每次都会跑一遍输入期望输出覆盖点3 4 5Right triangle直角、非等腰3 3 3Acute triangle / Isosceles triangle / Equilateral triangle三行全出3 3 4Acute triangle / Isosceles triangle等腰非等边、锐角4 4 6Obtuse triangle / Isosceles triangle等腰加钝角2 3 4Obtuse triangle钝角、无等腰1 2 3Not triangle退化边界1 1 2Not triangle退化、两短边相等6 8 10Right triangle缩放后的勾股数5 5 5Acute / Isosceles / Equilateral等边的另一种输入1 1 1Acute / Isosceles / Equilateral最小边界特别说明 4 4 6 这组4²4²326²3632 小于 36所以是钝角同时两边相等是等腰但不是等边。这组数据专门用来验证等腰 钝角能不能同时正确输出很多人只测锐角等腰漏了钝角等腰的情况。1 1 2 这组用来验证退化判定。如果代码里写的是而不是这组会被放行然后 1²1²22²4判成钝角再判等腰输出三行完全错误的结果。这组数据是抓这种 bug 的一把好手。5. 交上去之前再核一遍溢出、数据范围与调试习惯5.1 边长最大到多少平方会不会溢出数据范围决定了变量类型的下限这一步不能靠猜。假设边长上限是 10^4平方是 10^8两个平方相加是 2×10^8远在 int 上限 2.1×10^9 之内用 int 完全够。但如果上限提到 10^5平方就是 10^10直接溢出必须上 64 位。上限到 10^9 时平方是 10^18long long 的上限约 9.2×10^18仍然够用但已经接近了不能再往上加。除了平方还有一个更早爆的地方a[0] a[1]。如果边长接近 int 上限两个数相加就已经溢出此时比较结果毫无意义。所以稳妥的做法是全程用 long long 存边长读入时就用cin a[i]直接读进 long long 变量避免 int 中转。边长上限平方值平方和int 是否够用建议类型10^410^82×10^8够int10^510^102×10^10溢出long long10^910^182×10^18溢出long long养成先看范围再定类型的习惯比事后 debug 半天强得多。这道题本身的数据可能用 int 就能过但在真实项目里类型选错导致的时间戳溢出、金额计算失真是非常典型的线上事故从这种小题开始建立敏感度是值得的。5.2 自己造数据和对拍从 WA 到 AC 的排查路径提交后拿到 WA 而不知道错在哪最有效的手段不是盯着代码看而是构造能区分的测试数据。我的排查路径通常是这样的第一步确认基础分支。用 3 4 5、3 3 3、2 3 4 三组跑一遍看角类型分支是否都对。第二步单独验证退化判定用 1 2 3、1 1 2、2 3 5。第三步验证等腰和等边的包含关系用 3 3 3 和 3 3 4。第四步验证边界用全 1、全最大值的输入。如果手工数据都过但提交还错那大概率是格式问题或者大数溢出。格式问题可以用输出重定向到文件用文本工具逐字节看一遍重点检查有没有多余空格、少了换行、大小写不对。溢出问题就把所有变量临时改成 long long 再交一次如果过了说明确实是类型问题。还有一招更系统写一个暴力版本作为对照。比如角类型可以用浮点求角度的方法实现一个慢但直观的版本用随机数生成成千上万组数据两个程序同时跑比对输出。哪一组不一样问题就出在哪。这种对拍思路在任何判定题上都通用而且随着题目复杂度上升价值越来越大。5.3 这类判定题里最容易复发的几个错误把这类题目常见的坑集中列一下基本能覆盖新手 90% 的失败场景退化判定写成1 2 3 这类数据被放行。角类型三个判断用三个独立 if或者条件写成、导致输出多行。等腰和等边写成互斥等边数据少输出一行等腰。判断等腰时只写了a[0]a[1]漏掉a[1]a[2]3 4 4 这种数据被判成非等腰。字符串大小写或拼写错误比如把 Isosceles 写成 Isoceles。排除分支里忘了终止程序导致 Not triangle 后面又跟着输出别的行。变量用 int 且数据范围大平方和溢出成负数锐角判成钝角。其中第五条尤其值得说明。有些人用标志位的方式写if 不能构成 then flag false然后后面所有判断都加上if (flag)。这种写法本身没错但只要漏加一处就会出现不能构成三角形下面还跟着等腰三角形这种荒谬输出。直接 return 的最大好处是它在语法层面切断了后续代码的所有可能性你根本不需要记得加条件。写判定类题目有一句我常挂在嘴边的话先把所有不可能的情况排除干净剩下的自然就是答案。这道题的结构恰好是这个思路的完美示范——先排除不能成三角形的再在合法集合里三选一定角最后用两个独立条件判断边的关系。层次分明每一层只做一件事出错时也容易定位到是哪一层的问题。二十行代码里塞进去的这套思维比代码本身值钱得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑