资讯动态

打印图形题通用解法:坐标建模与叉积判断六芒星

发布时间:2026/9/16 2:05:21 来源:尧图企业网站定制
打印图形这类题在蓝桥杯里几乎是“常驻嘉宾”从最基础的三角形、菱形到后来的“打印大X”、字母沙漏、螺旋矩阵本质上都在考察一件事你能不能把一个二维平面上的几何形状用字符精确地复现出来。而六芒星算是我见过比较有代表性的一类——它不像三角形和菱形那样只有一个“主体”而是由两个三角形叠加交错而成中间还有重叠区域很多同学一上来用“逐行找规律”的土办法经常推着推着就乱套了。这篇文章我想换个角度讲不猜规律而是像计算机图形学那样把整个输出区域当成一张二维坐标纸用几何判断的方式直接“填充”字符。这种方法写起来代码量不大逻辑还特别清晰而且不止能解六芒星换任何多边形、任何旋转角度思路都能直接复用。特别适合正在准备蓝桥杯 Java/C 组、想系统搞定打印图形类题目的同学。1. 先把“打印图形”想成“给像素上色”1.1 字符画本质一张二维数组画布绝大多数打印图形题输出的都是一个由空格和可见字符组成的矩形区域。你不要把它想成“一行一行写出来的字符串”而要换成更底层的视角这就是一张二维的画布每个坐标点(row, col)只有两种状态——放字符或者放空格。一旦接受这个视角解题模型就变得非常统一根据输入参数确定画布的宽高。遍历画布上的每一个坐标点。用数学模型判断这个点“属于图形”还是“不属于图形”。属于就输出目标字符否则输出空格行末记得换行。这套流程和你在屏幕上画点、在 Excel 里给单元格涂色没有任何本质区别。六芒星之所以能让很多人卡住正是因为“逐行找规律”的思维在复杂图形里太容易出错而“逐点判断”的思路则把问题彻底标准化了。1.2 六芒星到底难在哪里六芒星六角星从几何结构上看是由两个完全相同的等边三角形中心对齐、旋转 180° 叠加而成的。它给人的第一印象是“对称”“好看”但落到代码里就出现了三个麻烦重叠逻辑两个三角形在中间区域会有交叉用逐行规律推的时候很容易在这个交叉区域重复计数或者漏算。斜线边界三角形的腰是斜的不像矩形那样“行内连续区间”好算每一行究竟是哪几个列有字符需要做一次直线方程的换算。对称性要求上下、左右都必须严格对称哪怕偏了一个空格输出结果肉眼可见地变形OJ 也会直接报 WA。这三个点本质上都可以用坐标几何来解决。你不需要在脑内演练每一行的字符分布只需要定义一个三角形区域然后问一句当前这个点在不在这个区域内在就输出星号不在就输出空格。1.3 两种解法路线的取舍我见过很多题解习惯把六芒星拆成“上三角 下三角 中间六边形”几个部分分别找规律。这个方法对特定形状是可行的但它的致命弱点是一旦题目改成旋转 30°、改成五角星、改成空心图形所有规律全部作废你得从头再推一遍。相比之下几何判断法是更通用的一劳永逸方案先确定图形的关键顶点坐标再用“点在多边形内”的判断方法统一渲染。这样哪怕题目再变态也只是改顶点坐标渲染代码一行都不用动。所以接下来的核心就是两件事第一怎么把六芒星的顶点坐标算出来第二怎么高效判断一个点在不在三角形内部。2. 几何建模用叉积判断点是否在三角形内2.1 六芒星的六个顶点怎么定六芒星是一个有六个尖角的星形这六个尖角恰好落在一个正六边形的六个顶点上。换句话说你只需要确定一个中心点和一个外接圆半径 R六个顶点就可以按照角度均匀分布上顶点90° 方向即 (cx, cy - R)右上顶点30° 方向即 (cx R * sqrt(3)/2, cy - R/2)右下顶点330° 方向即 (cx R * sqrt(3)/2, cy R/2)下顶点270° 方向即 (cx, cy R)左下顶点210° 方向即 (cx - R * sqrt(3)/2, cy R/2)左上顶点150° 方向即 (cx - R * sqrt(3)/2, cy - R/2)其中 sqrt(3)/2 ≈ 0.866025。然后构造两个三角形三角形 A上顶点、左下顶点、右下顶点这是一个尖朝上的三角形底边在中心下方水平放置三角形 B下顶点、左上顶点、右上顶点这是一个尖朝下的三角形底边在中心上方水平放置两个三角形中心完全重合方向相差 180°叠加以后就是标准的六芒星。这个建模方式的最大好处是无论 R 怎么变六个顶点都能由同一个公式算出来不需要针对奇偶性做特殊处理。2.2 二维叉积判断“点在三角形内”的原理高中数学里二维向量叉积的几何意义是“两个向量围成的平行四边形的有向面积”。它的计算公式是cross(P, A, B) (B.x - A.x) * (P.y - A.y) - (B.y - A.y) * (P.x - A.x)这里的顺序要记牢A 和 B 是线段两端点P 是要判断的点。叉积结果的正负取决于 P 在这条有向线段的哪一侧。判断点 P 是否在三角形 ABC 内部做法是分别计算 P 相对于三条边 AB、BC、CA 的叉积如果三个叉积全部同号全大于等于 0 或全小于等于 0说明 P 在这三条边的同一侧P 就在三角形内部包含边界。这个原理你可以用走路来类比沿三角形的三条边逆时针走一圈如果目标点始终在你的左手边那它一定在三角形里面如果有一段时间在你的右手边那它就在外面。用代码写就是static boolean inTriangle(Point a, Point b, Point c, Point p) { int r1 cross(a, b, p); int r2 cross(b, c, p); int r3 cross(c, a, p); return (r1 0 r2 0 r3 0) || (r1 0 r2 0 r3 0); }注意一个问题当三角形顶点坐标是浮点数时叉积结果也会是浮点数。由于浮点计算误差理论上的 0 可能会变成 1e-16 这样的极小值所以更稳重的写法是引入一个很小的容差 EPS判断条件写成r1 -EPS r2 -EPS r3 -EPS。2.3 为什么要用浮点坐标而不是纯整数有同学会问蓝桥杯的题不是尽量用整数避免精度问题吗为什么这里非要用浮点数因为六芒星的顶点坐标里天然带有 sqrt(3) 这个无理数。如果用整数近似替代比如把 0.866R 取成 0.87R图形在视觉上可能看不出差别但在精确对比输出的 OJ 环境下差一个字符都可能 WA。更稳妥的做法就是全程 double 运算只在最后判断的时候用容差吸收浮点误差。蓝桥杯的判题不会卡你 double 的性能放心用。3. 代码实现从伪代码到完整可提交的程序3.1 参数约定与画布大小假设题目输入一个正整数 R代表六芒星外接圆半径。画布的行数应该是 2R1因为最上方的顶点在 cy - R最下方的顶点在 cy R两边都算上列数则大约是 2 * R * sqrt(3)/2 * 2 1约等于 1.732R 1。为了保险起见我通常直接把列数定为 2R * sqrt(3) 3留一点边距保证图形不会溢出。把中心点放在 (cx, cy)一般取 cx Rcy R这样图形整体在一个正方形偏宽的区域内输出的视觉效果最均匀。3.2 Java 版完整代码实心六芒星下面这段是完整的 Java 实现逻辑分三层定义点、判断点在三角形内、遍历画布输出。import java.util.Scanner; public class Hexagram { static class Point { double x, y; Point(double x, double y) { this.x x; this.y y; } } // 向量叉积A-B 与 A-P static double cross(Point a, Point b, Point p) { return (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x); } static final double EPS 1e-9; static boolean inTriangle(Point a, Point b, Point c, Point p) { double r1 cross(a, b, p); double r2 cross(b, c, p); double r3 cross(c, a, p); return (r1 -EPS r2 -EPS r3 -EPS) || (r1 EPS r2 EPS r3 EPS); } public static void main(String[] args) { Scanner sc new Scanner(System.in); int R sc.nextInt(); sc.close(); double c Math.sqrt(3) / 2.0; // 0.866... int cx R; int cy R; int rows 2 * R 1; int cols (int) (2 * R * Math.sqrt(3)) 3; // 六个顶点按逆时针/顺时针排布均可 Point top new Point(cx, cy - R); Point rightUp new Point(cx R * c, cy - R / 2.0); Point rightDown new Point(cx R * c, cy R / 2.0); Point bottom new Point(cx, cy R); Point leftDown new Point(cx - R * c, cy R / 2.0); Point leftUp new Point(cx - R * c, cy - R / 2.0); // 两个三角形 Point[] tri1 {top, leftDown, rightDown}; Point[] tri2 {bottom, leftUp, rightUp}; for (int row 0; row rows; row) { StringBuilder sb new StringBuilder(); for (int col 0; col cols; col) { Point p new Point(col, row); boolean in1 inTriangle(tri1[0], tri1[1], tri1[2], p); boolean in2 inTriangle(tri2[0], tri2[1], tri2[2], p); sb.append(in1 || in2 ? * : ); } // 去掉行尾空格符合 OJ 常见输出规范 String line sb.toString().replaceAll(\\s$, ); System.out.println(line); } } }这段代码的核心就一个坐标判断函数inTriangle剩下的全是循环。你可能会注意到我没有手动处理“中心交叉区域重叠”的问题——不需要处理两个三角形都判断一次只要任意一个判断为真就输出星号重叠区域自动由 || 覆盖。3.3 输出效果与微调思路用 R6 运行你会看到类似这样的轮廓我这里为了排版简化了间隔实际跑出来是连续星号* *** ***** ******* ********** ************* ***************** ******************* ********************* *********************** ************************* *********************** ********************* ******************* ***************** ************* ********** ******* ***** *** *整体是个“上下尖、左右宽”的六角星形中间因为两个三角形叠加星号会比较密。如果你本地跑出来觉得六芒星不够“瘦”或者高度不足可以调整外接圆的采样方式比如把 y 方向的半径压缩到原来的 0.8 倍或者把 x 方向按字符宽高比做拉伸。这些都是视觉微调不影响提交正确性前提是你的题目没有规定严格的输出格式。3.4 只绘制边线的线条版很多题目的“打印图形”指的不是实心填充而是只画轮廓线看起来更清爽也更像手绘的六芒星。实现方式就是把判断条件从“点在三角形内”改成“点在三角形边上”。判断点是否在三角形边上的标准做法是先看叉积绝对值是否接近 0说明点在直线所在直线上再确认这个点是否在线段的包围盒内。但字符画的精度没必要这么精细更实用的做法是放宽距离当点到三条边中任意一条边的距离小于某个阈值时就认为它在边上。static boolean onTriangleEdge(Point a, Point b, Point c, Point p) { return distToSegment(a, b, p) 1e-7 || distToSegment(b, c, p) 1e-7 || distToSegment(c, a, p) 1e-7; }点到线段的距离需要先算投影点是否落在线段范围内落在区间外就取到端点的距离。这个函数代码量不大但是很多初学者容易写错投影点超出线段范围的情况。我建议的实现是先把边向量和点相对起点的向量做点积若点积小于 0返回点到起点的距离若点积大于边向量长度的平方返回点到终点的距离否则返回点到直线的垂直距离。不过如果你只是应付蓝桥杯不太建议在空心图形上花太多时间雕花。绝大多数打印图形题考的是“你能不能准确渲染一个区域”实心版本已经足够覆盖核心考点。4. 从六芒星到任意图形一个可以反复套用的模板4.1 改成旋转任意角度的版本六芒星的六个顶点本质是外接圆上的六个等间距点。所以只要你会算“圆上某个角度的点坐标”就可以让这个图案整体旋转任意角度。比如想把六芒星顺时针旋转 30°让两个尖角变成左右朝向只需要在计算顶点坐标时给每个角度加上 30° 的偏移。计算公式是x cx R * cos(angle) y cy R * sin(angle)其中六芒星的六个顶点对应的初始角度是 90°、30°、-30°、-90°、-150°、150°或者随便一个等差数列也行。旋转就是把每个角度统一加上 delta。这个模板我愿称之为“打印图形题的终极解”只要你能把图形的关键顶点坐标算出来剩下的全是复制粘贴。从三角形、四边形到五角星、齿轮、雪花通通可以套同一个inPolygon判断。4.2 指定输出字符与行尾空格规范蓝桥杯的打印图形题经常会在输出格式上做文章。比如题目要求用字符#而非*填充或者要求行尾不能有多余空格。我的习惯是先把图案放进一个二维 char 数组或者用 StringBuilder 逐行生成最后统一处理输出。这样既方便调试也方便对每一行做trim()之类的操作。因为如果边判断边输出一旦发现格式不对要改的地方会很多而先生成再处理逻辑就集中在一个地方。用字符数组还有一个额外的好处你可以先全部填充空格再把需要放置星号的位置填上字符最后一次性输出。遇到需要“先画背景再画图形”的复杂题目这种思路尤其顺手。char[][] canvas new char[rows][cols]; for (char[] row : canvas) { Arrays.fill(row, ); } // 逐点判断决定是否填充 canvas[row][col] *; // 输出时去掉尾部空格4.3 扩展到任意多边形如果你已经理解了“点在三角形内”的判断那么扩展到“点在任意凸多边形内”只差一步把这个多边形拆成若干个三角形任意一个三角形包含该点即可。至于凹多边形也可以用射线法从点朝任意方向发出一条射线统计与多边形边的交点数奇数则在内部来统一处理。射线法实现起来比叉积法还要简单但因为涉及少量浮点计算和边界特判应付竞赛题时不如三角形叉积直观。我的建议是如果你的题目明确是三角形、两个三角形叠加、或者多个三角形组合直接用本篇文章的叉积法最省事如果题目给出的是任意多边形坐标再考虑换射线法。把这两种方法都掌握打印图形这类题在蓝桥杯里基本就是送分题了。5. 常见问题与排查技巧实录5.1 六芒星打印错乱问题速查表现象可能原因解决办法打印出三角形但不是六芒星两个三角形的顶点关系不对底边没有交叉检查三个顶点是否为“上、左下、右下”和“下、左上、右上”不要自己乱配图形上下颠倒行坐标取反或顶点方向反了把 row 当成 y 坐标时注意图形顶部对应 y 小底部对应 y 大星号连成一片看不出尖角在三角形内部判断时把 顶点处缺角、边缘断线容差 EPS 设置过大把边界点过滤掉了EPS 保持 1e-7 到 1e-9 即可不要设置成 0.1输出尾部多空格被判错没有去掉每行的行尾空格输出前用replaceAll(\\s$, )或逐字符判断最后有效位图形整体偏到一侧画布宽度不够或中心点设置偏差把中心点放在 (R, R) 附近画布宽度留足够边距5.2 本地调试三步法我在练习这种题时基本固定用三步调试第一步从 R2 或 R3 这种小尺寸开始。图形越小越容易一眼看出顶点和边界是否错位。第二步临时输出“坐标编号”而不是星号。比如把画布里每个坐标点的行列号打出来或者用不同的字符标记不同的三角形。这样做可以快速定位是哪个三角形判断出了问题。第三步单独验证inTriangle函数。随便选三个顶点人为构造几个明显在内部和外部的点打印叉积结果确认符号判断逻辑没有写反。这个函数一旦可靠剩下就是体力活。5.3 考场上别在打印题上恋战打印图形题在蓝桥杯中一般属于中低难度分值不会特别高。如果一道打印题你推了十几分钟还没思路我的建议是先跳过去做后面的贪心、动态规划、搜索题。这些题虽然难但只要你受过训练产出比往往比死磕打印题更高。当然如果你掌握了本文的坐标建模法很多打印题其实一分钟就能写完框架剩下只是微调坐标。熟练以后这类题反而会成为你稳定拿分的项目。最后分享一个我自己的做法每次遇到打印图形题我都会在草稿纸上先把图形的“关键顶点”列出来比如六芒星就是六个外接圆顶点再圈出要用哪些三角形。顶点一确定代码就成功了一半。这个方法跟图论里“先建模再跑算法”是同一个思路——不要一上来就闷头找规律先把数学模型立住后面都是水到渠成的事。

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

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

免费获取报价