目录
3.1 生成n*m大小的菱形(本质上在第二种变化基础上增加了角度变化)
前言
对于初学C语言的人而言,生成菱形一直都是需要跨过的一道坎,在这篇文章里,我将详细的讲解我对生成菱形的思路以及其背后的数学逻辑与方法,其中包括了固定大小菱形的生成到变大小菱形的生成到变角度菱形的生成,希望对看这篇文章的人都有所帮助。
附:样例
1.1 固定生成5*5大小的菱形(最简单最基本的图形生成)
2.1 生成n*n大小的菱形(本质上缩小或增大了基本图形)
3.1 生成n*m大小的菱形(本质上在第二种变化基础上增加了角度变化)
我们先从最简单的开始做起,以便进行生成复杂图形的推导
一. 固定生成5*5基本大小的菱形(后文中称其为基础菱形)
1. 分析原理
1.1 代码逻辑
我们可以通过观察发现菱形是由一个个空格与填充符号所构成的,那么我们只需要找出行数增加时的空格数与填充符号数增加的规律,并用for循环语句进行打印,就可以得到菱形。因此,我们需要设置行变量i,空格数变量j,填充符号数变量k。
1.2 数学逻辑
可以看到菱形上半部分的空格数是依次递减,而下半部分是依次递增,填充符号数则反之,其变化规律类似于分段函数,因此,在描述空格数与填充符号数的规律时,我们分菱形的上半部分和下半部分。
1.3 逻辑实现
①菱形上半部分空格数
通过观察,我们发现随着行数i的递增,上半部分菱形的空格数变化情况为:
行数(i) 1 2 3 空格数 2 1 0 观察得出规律:上半部分的空格数=3-i ①
对应代码如下:for (int j = 1; j <= 3 - i; j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 }
②菱形上半部分填充符号数
通过观察,我们发现随着行数i的递增,上半部分的填充符号数呈奇数列:
行数(i) 1 2 3 填充符号数 1 3 5 观察得出规律:上半部分的空格数=2*i-1 ②
对应代码如下:for (int k = 1; k <= 2 * i - 1; k++) {//设置打印符号的循环次数,k表示符号填充行 printf("%c", ch);//打印符号 }
③菱形的下半部分空格数
通过观察,我们发现其空格数与行数减去3恰好相等:
即:下半部分空格数=i-3 ③
行数(i) 4 5 空格数 1 2 对应代码如下:
for (int j = 1; j <= i - 3; j++) { printf(" ");//打印空格 }
④菱形下半部分的填充符号数通过观察,我们发现其填充符号数呈倒奇数列的形式,看到奇数列,依旧想到2n-1(n=1,2,3,......)的数学形式,在当前情况中,很显然n=i-3,但由于此时是倒奇数列,因此我们需要一个正偶数来减去奇数列来得到它,即4-(2*(i-3)-1)。
化简得出结果: 下半部分的填充符号数=11-2*i ④
行数(i) 4 5 空格数 3 1 对应代码如下:
for (int k = 1; k <= 11 - 2 * i; k++) { printf("%c", ch); }
2.代码实现
总体代码如下:
void UtimateDiamond() { char ch = 0;//初始化变量,设置一个变量使其可以自由变换所用填充菱形的符号 printf("\n请输入要填充图形的符号:"); scanf("%c", &ch);//输入符号 for (int i = 1; i <= 5; i++) {//i表示当前行数 if (i <= 3) {//判断是否为菱形上半部分 for (int j = 1; j <= 3 - i; j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 } for (int k = 1; k <= 2 * i - 1; k++) {//设置打印符号的循环次数,k表示符号填充行 printf("%c", ch);//打印符号 } } else{//若i已经超过3,则说明此时为菱形下半部分 for (int j = 1; j <= i - 3; j++) { printf(" ");//打印空格 } for (int k = 1; k <= 11 - 2 * i; k++) { printf("%c", ch); } } printf("\n");//每一次循环结束换行,进行下一行的打印 } }
现在逐级增加难度
二. 生成n*n大小的菱形(即对基础菱形放大或缩小)
1.分析原理
1.1 代码逻辑
为了实现菱形的放大或缩小,很显然菱形的行数就成为了一个变量,由于此时菱形的行数和列数在变化中一直相等,因此,我们只需设置一个行数变量line。可知,当line=5时,此菱形为基础菱形,line增大时,即对基础菱形放大,line缩小时,即对基础菱形缩小【注意:line的最小值为可以生成的最小菱形的行数值,即line>=3,且line一定是奇数】。
1.2 数学逻辑
核心逻辑不变,为了确定当引入line变量后菱形上半部分和下半部分的分界线,我们需要对其公式进行推导,在纯数学领域上来说,上半部分的行数=line/2+0.5,但对C语言来说,由于line是int类型,2也是int类型,因此line/2也是int类型,其结果会丢失小数部分,所以再加0.5才能得到上半部分的行数,上半部分的行数=line/2+1。由于此时行数成为了一个变量,在必要时,我们可以画出多个line值情况下的菱形来找出行变量,空格数变量以及填充符号数变量的关系。
1.3 逻辑实现
①菱形上半部分空格数
通过观察,我们发现随着行数i的递增,上半部分菱形的空格数变化情况为:
行数(i) 1 2 3 ... line/2+1 空格数 line/2 line/2-1 line/2-2 ... 0 表格数据中的表达式并不是很容易直接想到,这时我们可以多画两个菱形示意图,分别按照基本菱形的方法列出上半部分的空格数的表达式去发现其中的规律,如下是 line=5 和 line = 7 以及 line = 3 时的菱形图像,其表达式按照基本菱形的方法得出的空格数表达式分别为:3-i , 4-i , 2-i ,推断出上半部分的空格数为上半部分总行数减去当前行数i,即上半部分空格数=line/2+1-i。
line 5 7 3 空格数表达式 3 - i 4 - i 2 - i ![]()
line=3 ![]()
line=5 ![]()
line=7
观察得出规律:上半部分的空格数=line/2-i+1 ⑤
对应代码如下:for (int j = 1; j <= line / 2 + 1 - i; j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 }
②菱形上半部分填充符号数
通过观察,我们发现随着行数i的递增,上半部分菱形的填充符号数变化情况为:
行数(i) 1 2 3 ... line/2+1 填充符号数 1 3 5 ... line 很显然,上半部分的填充数=2*i-1 ⑥
对应代码如下:
for (int k = 1; k <= 2 * i - 1; k++) {//设置打印符号的循环次数,k表示符号填充行 printf("%c", ch);//打印符号 }
③菱形的下半部分空格数
依照上半部分空格数推导的经验来看,我们需要画出多个菱形示意图进行,分别参照基本菱形的方法找出相应的下半部分空格数表达式,老样子,还是使用line=3,line=5,line=7来举例,通过比较发现其下半部分空格数为当前行数减去菱形上半部分行数,即下半部分空格数 = i - (line / 2 + 1)
line\行数(i) 3 4 5 6 7 3 1 0 0 0 0 5 无效 1 2 0 0 7 无效 无效 1 2 3 因此得出规律:下半部分的空格数 = i - line / 2 - 1 ⑦
对应代码如下:for (int j = 1; j <= i - line / 2 - 1; j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 }
④菱形下半部分的填充符号数
按照惯例,我们准备用找数字规律的方式进行公式推导式就比较吃力了,因为在使用基本菱形中的方法中,我们在确定正偶数时方法是通过为了得到倒奇数列的结果而进行的取值,这样的取值方式与我们的菱形没有多少关联,因此我们得从图像入手,寻找图像里更普适的规律。
![]()
line=7 这时我们注意到了菱形的对称性,既然菱形是对称的,那么其左右两边的空格数也应该相等,也就是说,只要知道了菱形一边的空格数,就能用总列数减去二倍当前行数的空格数,就能得到当前行的填充符号数,写成公式即:填充符号数=总列数-2*当前行的空格数,这个式子甚至适用于整个菱形,而不是一半的菱形,由于这个公式中的当前行空格数表达式已经被我们提前算出,因此这个式子也没有未知量。
按照上述推导公式,我们的下半部分填充符号数=line-2*(i-line/2-1)。
最终化简得到,下半部分填充符号数=2*line-2*i+1 ⑧
对应代码如下:
for (int k = 1; k <= 2 * line - 2 * i + 1; k++) {//设置打印符号的循环次数,k表示符号填充行 printf("%c", ch); }
2.代码实现
总体代码如下:
bool ZoomDiamond() { char ch = 0;//初始化变量,设置一个变量使其可以自由变换所用填充菱形的符号 int line = 0;//初始化对角线长度 printf("\n请输入要填充图形的符号:"); scanf("%c", &ch);//输入符号 printf("\n请输入你要的菱形的对角线长度(注意要大于2且只能是整奇数):"); scanf("%d", &line);//输入对角线长度 if (line < 3 || (line * 10) % 10 != 0 || line % 2 == 0) {//判断line是否大于3或者是否为整奇数 printf("\n******非法输入******\n"); return false; } for (int i = 1; i <= line; i++) {//i表示当前行数 if (i <= line / 2 + 1) {//判断是否为菱形上半部分 for (int j = 1; j <= line / 2 + 1 - i; j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 } for (int k = 1; k <= 2 * i - 1; k++) {//设置打印符号的循环次数,k表示符号填充行 printf("%c", ch);//打印符号 } } else {//若i已经超过line/2+1,则说明此时为菱形下半部分 for (int j = 1; j <= i - line / 2 - 1; j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 } for (int k = 1; k <= 2 * line - 2 * i + 1; k++) {//设置打印符号的循环次数,k表示符号填充行 printf("%c", ch); } } printf("\n");//每一次循环结束换行,进行下一行的打印 } return true; }
终于到最终难度了
三. 生成n*m大小的菱形(即改变菱形的角度)
1.分析原理
1.1 代码逻辑
改变菱形的角度,也意味着菱形的行和列已经不相等了,因此我们设置两个变量line与column分别表示总行数和总列数【行和列均为奇数且大于3】。
1.2 数学逻辑
为了打开突破口,我们依旧需要先观察多个变化的菱形来找出它们之间的内在联系。使用控制变量法先将line定为5,变化column,得到以下几个图形:
![]()
图一 ![]()
图二 ![]()
图三 ![]()
图四
column变化时各行的空格数
i 1 2 3 4 5 图一 2 1 0 1 2 图二 4 2 0 2 4 图三 6 3 0 3 6 图四 8 4 0 4 8 很显然,在column增加的过程中,菱形之间的空格增长数有倍数关系,菱形自身的每一行的空格增长有倍数关系,同时,column增长的过程中,为满足整个图形成菱形,不能以自增1的方式增加列数,因此,我们设置一个表示这个倍数的变量offset(在后文称做菱形的偏移量)。偏移量的大小代表了菱形中每到下一行时,减少的空格数的数目,其值一定大于0且为整数。例如对图一来说,偏移量为1,对图二来说,偏移量为2,对图三来说,偏移量为3。可以发现:偏移量越大,则菱形越扁,column越大,且如果说图一为基本菱形,那么图二,图三,图四与基本菱形的相应行的空格数是其本身菱形的偏移量倍,由此我们在n*n菱形的基础上加入offset变量进行公式变形从而获得其菱形的空格数。
1.3 逻辑实现
①菱形上半部分空格数
按照上面数学逻辑的推论,我们可以直接对公式⑤进行变形,加入offset量后,可以得到菱形上半部分的空格数=offset*(line/2+1-i)。
化简后得到,上半部分空格数=offest*line/2+offest-offest*i ⑨
[补充:还记的上面写的column不能随意增加的事吗?它与offest有关系,观察菱形可以得知,第一行空格数*2+1=总列数,因此column=offset*(line-1)+1,所以在输入参数时,我们只需要输入line与offest即可]
其对应代码为:
for (int j = 1; j <= (offest * line / 2 + offest - offest * i); j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 }
②菱形上半部分填充符号数
这里的菱形显然不满足连续的奇数列了,这时我们要想到之前我们所推导出来的填充符号数=总列数-2*当前行的空格数,由此,我们可以得到菱形上半部分的填充符号数=column-2*(offset*(line/2+1-i))。
化简后得到,上半部分的填充符号数=column-offest*line+2*offest*i-offest ⑩
其对应代码为:
for (int j = 1; j <= (offest * i - line / 2 * offest - offest + offest / 2); j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 }
③菱形的下半部分空格数
与上半部分的空格数推导过程相似,下半部分的空格数目也满足基本菱形的偏移量倍,对⑦式变形可知,下半部分的空格数=offest*(i - (line / 2 + 1))。
化简可知:下半部分的空格数=offest*i-line/2*offest-offest+offest/2【注意这里不能将offset合并同类项,否则会出现问题】
其对应代码为:
for (int j = 1; j <= (offest * i - line / 2 * offest - offest + offest / 2); j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 }
④菱形下半部分的填充符号数
同样使用填充符号数=总列数-2*当前行的空格数,可知:下半部分的填充数=column-2*offest*(i - (line / 2 + 1))。
化简可得:下半部分的填充数=column-2*i*offest+offest*line+offest
其对应代码为:
for (int k = 1; k <= (column - 2 * i * offest + offest * line + offest); k++) {//设置打印符号的循环次数,k表示符号填充行 printf("%c", ch); }
2.代码实现
总体代码如下:
bool NormalDiamond() { char ch = 0;//初始化变量,设置一个变量使其可以自由变换所用填充菱形的符号 int line = 0, column = 0;//初始化行数和列数 int offest = 0;//初始化菱形的偏移量 printf("\n请输入要填充图形的符号:"); scanf("%c", &ch);//输入符号 printf("\n请输入你要的菱形的行数(注意要大于2且只能是整奇数):"); scanf("%d", &line);//输入行数 if (line < 3 || (line * 10) % 10 != 0 || line % 2 == 0) {//判断line是否大于3或者是否为整奇数 printf("\n******非法输入******\n"); return false; } printf("\n请输入你要的菱形的偏移量(注意要大于0且只能是整数):"); scanf("%d", &offest);//输入菱形偏移量 if (offest < 1 || (offest * 10) % 10 != 0) {//判断offest是否为整数且大于1 printf("\n******非法输入******\n"); return false; } column = offest * line - offest + 1;//计算在当前条件下的列数 for (int i = 1; i <= line; i++) {//i表示当前行数 if (i <= line / 2 + 1) {//判断是否为菱形上半部分 for (int j = 1; j <= (offest * line / 2 + offest - offest * i); j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 } for (int k = 1; k <= (column - offest * line + 2 * offest * i - offest); k++) {//设置打印符号的循环次数,k表示符号填充行 printf("%c", ch);//打印符号 } } else {//若i已经超过line/2+1,则说明此时为菱形下半部分 for (int j = 1; j <= (offest * i - line / 2 * offest - offest + offest / 2); j++) {//设置打印空格的循环次数,j表示空格行 printf(" ");//打印空格 } for (int k = 1; k <= (column - 2 * i * offest + offest * line + offest); k++) {//设置打印符号的循环次数,k表示符号填充行 printf("%c", ch); } } printf("\n");//每一次循环结束换行,进行下一行的打印 } return true; }
结言
第一次在上面发表技术文章,我兴奋的打了差不多一天的字,好累ㅠ ㅅ ㅠ,但是写完之后又是满满的成就感。
如果你看完了这篇文章,你可能有所疑惑,为什么不一开始就用最好的结论,而非要去找规律,其实我的想法是,思路和技巧都是慢慢积累的,最开始的时候谁也不知道有这么简单的结论,我就想着把自己的思考过程展示给大家,由简入繁,我们学习任何一个东西都不可能一蹴而就,我们大多数的人,最初的学习都是从显而易见的道理开始,然后逐渐熟悉后在此基础上领悟出新的道理。这也是我写这篇文章想要表达的事情。漫漫人生路,何尝也不是如此呢?
最后,诚邀大家对我的文章斧正或改进,感激不尽。