什么是水仙花数?
请参见:http://baike.baidu.com/view/152077.htm
在传统算法中,需要花费大量的时间进行计算,一个是进行幂运算,一个是进行数字的拆分判断。
这个算法在两个地方上进行了改进:
1。 幂运算用一个数组缓冲,这样,只有位数变化的时候,才需要刷新一次缓冲,而且是乘法,不是幂运算,
有了这个缓冲,幂运算就变成取数组元素的代码。
这个改进的前提是,我们只需要0~9的n次幂,所以个数是固定的
2。判断是否水仙花数,用差值比较,而不是直接判断
原理是这样的:
假设一个数A=a(N)a(N-1)...a(0) 是一个水仙花数
如果另一个数B=b(M)b(M-1)....b(0)也是一个水仙花数 其中 a(x)表示A在第x位上的数字,b(x)也是
那么,因为 A,B的特性,可以得到
A-B=[a(N)^N+...a(0)^N ] - [b(M)^M+...b(0)^M] (式1)
为什么要搞成这个样子呢? 因为,如果N=M,A与B的大多数位的数值相同,那么我们只需要计算A、B数字不相同的位就可以判断是否水仙花数。
因为 A-Z=A-B+B-C+C....+Y-Y-Z = (A-B)+(B-C)+(C.....-Y)+(Y-Z)
所以,计算差值的时候,可以把每次的差值累计起来计算。
因此,我们来看,假设循环每次递增为1,那么,90%的情况下,只有1位数值发生了变化。 判断这个数是不是水仙花数的时候,不需要把所有位上的数字的N次方的值相加再和数字本身比较,而是仅仅计算发生了变化的这一位数字的N次方,和上一个水仙花数的对应位的M次方的差,如果(式1)成立,那么这个数就是一个新的水仙花数。而因为只有一位发生了变化,所以(式1)可以简化成
A-B=a(x)^N-b(x)^M (式2)
N是A的位数,M是B的位数。
这样一来,几乎90%的计算只需要做1位的加减法。前面也说过了,“如果N=M,A与B...”,所以在N!=M的情况下,需要从新调整差值。
因此,编写代码如下,主要的思想是利用每次判断某个数是否水仙花数的时候的计算结果,减轻下一次计算的负担。
使用这个程序计算10位以下的水仙花数,结果如下:
153
370
371
407
1634
8208
9474
54748
92727
93084
548834
1741725
4210818
9800817
9926315
24678050
24678051
88593477
146511208
472335975
534494836
912985153
Used 32.929 Second
而使用简单的每次判断,则运算时间稍长:
153
370
371
407
1634
8208
9474
54748
92727
93084
548834
1741725
4210818
9800817
9926315
24678050
24678051
88593477
146511208
472335975
534494836
912985153
Used 66.258 Second
这个优化的效果在9位以下时,效果不明显,以下是计算时间比较:
计算位数 | 简单算法时间(秒) | 优化算法时间(秒) | 循环花费时间(秒) |
3 | 0 | 0 | 0 |
4 | 0 | 0 | 0 |
5 | 0.016 | 0 | 0 |
6 | 0.047 | 0.031 | 0.016 |
7 | 0.546 | 0.328 | 0.219 |
8 | 6.032 | 3.313 | 2.500 |
9 | 66.258 | 32.929 | 28.926 |
10 | 714.458 | 330.422 | 330.167 |
11 | 。。。 | 。。。 | ... |
上表中的“循环花费时间”是指不做判断,只是对数字作++运算。
例如,位数=9,这一行,简单算法共用时66.258秒,其中循环花费时间28.926秒,那么,用于判断的时间为:66.258-28.926=37.328秒;
而同时,优化算法共用时32.929秒,那么判断的时间为32.929-28.926=4.003秒。
所以从这里看,数位越大,循环本身花费的时间越成为性能瓶颈。 如果能够提高循环的效率,比如,每次不是增加1,而是增加10以上的一个数,那么效率还可以提高10倍。
我想了一天,也没想到怎么确定递增的步长。 希望高手们继续研究啊
==========================================
2010-10-12
----------------------------------------------------
有研究了一天,终于得到一个增加递增步长的办法。
原理是根据差值计算方法判断一个数是不是水仙花数的时候,从个位数为0开始,如果在10个数范围内有一个水仙花数的话,此时计算的sum[a(x)^n-b(x)^m]与A-B的差值一定是固定的。 因为,在这10个数内,sum[a(x)^n-b(x)^m]只会有x=0的变化,以个位数=4时变成水仙花数为例,假设个数+4是水仙花数,那么新增的差值就是 4^n+4. 所以,如果个位数为0时,
sum[a(x)^n-b(x)^m]与A-B的差值刚好等于 4^n+4, 也就是 sum[a(x)^n-b(x)^m] - (A-B) = 4^n+4
那么,在个位数变成4后, sum[a(x)^n-b(x)^m]的变化就是
sum[a(x)^n-b(x)^m] + a(0)^n , 其中a(0)=4,
所以上式变成
(A-B)+4^n+4+4^n = 4+(A-B)
而同时,A-B的值,B一直没有变化,A从一个个位数=0的数,变成个位数=4的数,那么A-B也就变化了4,也是(A-B)+4
所以新的sum[a(x)^n-b(x)^m]=新的A-B,
所以,可以根据个位数=0的时候计算得到的 差值,直接判断出这10个数内有没有水仙花数。
据此修改代码的步长计算方法,运算时间比较如下:
计算位数 | 简单算法时间(秒) | 优化算法时间(秒) | 步长优化后 算法时间(秒) | 循环花费时间 (秒,步长=1) |
3 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 |
5 | 0.016 | 0 | 0 | 0 |
6 | 0.047 | 0.031 | 0.016 | 0.016 |
7 | 0.546 | 0.328 | 0.094 | 0.219 |
8 | 6.032 | 3.313 | 0.875 | 2.500 |
9 | 66.258 | 32.929 | 8.323 | 28.926 |
10 | 714.458 | 330.422 | 84.955 | 330.167 |
11 | 。。。 | 。。。 | ...... | ... |
附代码如下