【程序设计】3.2 [做循环] 多层循环
3.2 多层循环
就像 if 和 else 的代码块中还可能再有 if…else 一样,for 的循环体中也还可以再有 for,这种情况我们称为多层循环。
水仙花数是指这样的一个三位数,其每一位数字的立方加起来之后正好等这个数本身,现在我们想计算出 100-999 之间所有的水仙花数。
A |
B |
C |
D |
E |
|
1 |
for 9 |
=100*A1 |
=A1*A1*A1 |
||
2 |
for 0,9 |
=B1+10*B2 |
=C1+B2*B2*B2 |
||
3 |
for 0,9 |
=C2+C3 |
=D2+C3*C3*C3 |
||
4 |
if D3==E3 |
>output(D3) |
我们现在还没有学习到集合概念,不能把找到的所有水仙花数保存在一个集合中。所以就在 E5 格把找到的水仙花数用 output 函数输出了,这样可以在右下角看到。
B2 和 C3 出现了新语法形式 for 0,9。通常,语句 for n 表示循环变量将从 1 变到 n,但有时我们希望起止值变一下,也可以把循环语句写成 for a,b 的样子,表示循环变量将从 a 变到 b,这里 a 和 b 都是整数。如果 b>a,循环变量取值将是 a,a+1,…,b,总共循环 b-a+1 次,如果 b<a,则循环变量取值将是 a,a-1,…,b,总共循环 a-b+1 次。
A1、B2、C3 三格分别针对三位数的每一位数字进行循环,这是段三层循环的代码。最外层 A1 循环百位数,中间层 B2 循环十位数,最内层 C3 循环个位数。因为十位和个位数都是从 0 到 9 这 10 种可能,如果写成 for 10,则在后面运算时还需要减 1,比较麻烦,所以直接写成 for 0,9 省事。
多层循环时,外层循环时在执行每一次循环体时,都会把内层循环完整的执行一遍。这样 A1 会循环 9 遍,B1 也会被执行 9 遍; B2 本身会循环 10 遍,C2 作为 B2 的循环体内语句,会被执行 9*10=90 遍;D3、E3 作为 C3 的循环体内语句,则会执行 9*10*10=900 遍。
D3 和 E3 格分别计算出这个三位数和每位的立方和。而 B1、C1 以及 C2、D2 的计算,是试图让计算量变小一点,因为个位数循环变化时,百位和十位数是暂时不变的,没必要每次都把针对百位和十位的计算重来一遍。这也是写代码时要注意的事项,内层循环体的语句,其执行次数可能非常多,能少一点计算量就会使总的计算量下降很多,从而大幅度提升程序的性能。
现在可以自己执行一下,看看右下的输出,计算出哪些水仙花数。
顺便说个题外话,我们在这里计算立方时,用的是乘法。前面讲二次方程时算平方也是用乘法,为什么不直接使用 power() 函数?
其实前面算平方时是可以用 power()的,但这里不行。因为 power() 是使用指数和对数计算的,它会返回一个浮点数,而这里需要精确对比,用浮点数就不太可靠了,所以宁可费点事写成自乘三遍。也因为这个,我们要写成 for 0,9,不然用 for 10 再写 B2-1 自乘三遍也有点费事。
前面的平方没有用 power(),一方面自乘两遍还不难写,另一方面用指数和对数计算速度会慢很多,所以也就写成自乘了。
这些也是编程时常常要注意的细节。
这个代码还可以优化一下。一个三位数,如果三个位上的数字的立方和已经大于这个三位数了,个位数再循环下去就没有必要了。因为百位和十位不变时,个位数循环到下一个,即再增加 1 时。这个立方和至少会增加 1(只有个位数从 0 变 1 时增加 1,其它变化都大于 1),而这个三位数也只增加 1。那么,这个三位数与这个立方和之间的差距不可能变得更小了(一般会越拉越大),也就不可能相等了。
也就是说,一旦我们发现 D3<E3 时,C3 的循环就没有必要继续下去了,不可能在 A1 和 B2 不变的情况下找到水仙花数了,再算下去只是浪费时间和计算量了。
包括 SPL 在内,大部分程序语言都提供有 break 语句用于中止当前的循环,代码改写为:
A |
B |
C |
D |
E |
|
1 |
for 9 |
=100*A1 |
=A1*A1*A1 |
||
2 |
for 0,9 |
=B1+10*B2 |
=C1+B2*B2*B2 |
||
3 |
for 0,9 |
=C2+C3 |
=D2+C3*C3*C3 |
||
4 |
if D3==E3 |
>output(D3) |
|||
5 |
else if D3<E3 |
break |
这个代码多了一行,但计算性能比之前好了很多。
其实,刚才针对个位数的分析对于十位数也成立,因为 0-9 这些数字的立方一定不小于它本身,如果 C2<D2,那么就一定会有 D3<E3,这时候第三层循环根本没必要做了。
代码可以再优化:
A |
B |
C |
D |
E |
F |
|
1 |
for 9 |
=100*A1 |
=A1*A1*A1 |
|||
2 |
for 0,9 |
=B1+10*B2 |
=C1+B2*B2*B2 |
if C2<D2 |
break |
|
3 |
for 0,9 |
=C2+C3 |
=D2+C3*C3*C3 |
|||
4 |
if D3==E3 |
>output(D3) |
||||
5 |
else if D3<E3 |
break |
上面的分析再延伸到百位数上却没什么意义,请自己想明白这里的道理。这段代码的优化基本上到此为止了。