赞
踩
这一篇博客的价值,在于使用了一种处理非常大的数字乘积的方法,这些数字是存储在数组中的,这样,理论上,处理多大的数相乘,都不会有任何问题。(然而实际测试过程中,发现Python内部处理乘法的逻辑,已经采用类似的方法了,于是,理论上对阶乘的大小是没有限制的了……)
求输入给定整数的阶乘。
阶乘,给定一个整数 n n n, n ! = n × ( n − 1 ) × ( n − 2 ) × . . . × 1 n!=n\times(n-1)\times(n-2)\times...\times1 n!=n×(n−1)×(n−2)×...×1
对于 n ! n! n!,其实可以看成 n × ( n − 1 ) ! n\times(n-1)! n×(n−1)!,根据这个递推公式,既可以直接写出递归求解的程序
# 算阶乘,递归的方法
def factorial(n):
if n == 1: # 递归结束条件
return 1
else:
return n*factorial(n-1)
1.注意一定要有递归结束的条件:if n==1
2.对于每一种编程语言来说,都有默认的递归调用深度,对于Python,测试后是998层,具体为什么是998,不得而知,可能跟程序的规模大小有关。可能程序越复杂,能递归调用的层数也会相应减少吧。
3. 递归调用的程序,看起来简洁,但不一定高效,如求解斐波那契数列,用递归解决很简单,但该方法却什么低效,之后会具体介绍。
能用递归调用解决的方法,都能用循环解决。对于阶乘,无非就是建立一个for循环,根据定义,依次把数字乘起来即可。
# 用循环来解决
def factorial_loop(n):
result = 1
for i in range(1, n+1):
result *= i
return result
注意range(n)
,并不包括
n
n
n, 因此循环的终点要设为
n
+
1
n+1
n+1
阶乘的结果增长很快,一般计算机用于存储整数最大去到64位,对应于十进制数是 1.84467441 × 1 0 19 1.84467441\times10^{19} 1.84467441×1019, 也就是说没如果阶乘结果的位数超过19,计算机会溢出,所谓的溢出,就是数值的高位被舍去,结果反而变小了。下面是教材演示的溢出现象。
教材用的是 int 型数据,对应32位,最大的整数值是
2
,
147
,
483
,
647
2,147,483,647
2,147,483,647, 而下面
13
!
=
6
,
227
,
020
,
800
13!=6,227,020,800
13!=6,227,020,800, 结果是存不进去的。
不过,我直接用Python测试,发现Python已经解决了这样的问题。在我算到 998 ! 998! 998!的时候,递归算法才因为超过递归深度而无法计算,而没有出现溢出的显现。
经过测试,Python是优化过大数的乘法运算的,下面多个测试,完全无障碍。
500
!
500!
500! 毫无问题
997
!
997!
997! 这是递归算法能hold住的最后一个数,对于循环求解算法,唯一的限制或许就是计算机本身的存储能力了吧。
998
!
998!
998!, 递归溢出。对于递归算法,使用的时候,必须记得测试最大递归深度是多少,使用递归的快排算法,也会面临递归深度溢出的问题。
# 用教材的方法,使用数组存储 # 这里有两个部分,第一个函数,处理进位的函数,是最重要的一个;第二个,处理遍历各位的信息, # 但依赖于函数一 def carryHandle(listOfNum, numOfBit): # 参数1是存储乘积结果的矩阵,参数2是存储乘积的倍数 carry = 0 for i in range(numOfBit+1): listOfNum[i] += carry # carry是进位 if listOfNum[i] <= 9: carry = 0 # 表明不用进位 elif i < numOfBit: # numOfBit 记录的也是索引 carry = listOfNum[i] // 10 # 因为是十进制,多的进位 listOfNum[i] = listOfNum[i] % 10 # 一个数组位,保留一位就好 else: # 正常能进来这里,就是 listOfNum[i] > 9, 并且是最高位 while listOfNum[i] > 9: carry = listOfNum[i] // 10 listOfNum[i] = listOfNum[i] % 10 i += 1 listOfNum[i] = carry # 把上面的进位,继续加上去 # 此处传的是数组,所以不用返回 def factorial_LargeNumber(num): # # 确定结果的位数,建立相应的数组 sumOfBits = 0 for i in range(1, num+1): sumOfBits += math.log10(i) # log2则是求比特的位数,因为是10进制,所以对10求log;此时是实数 digit = int(sumOfBits)+1 # 最长的结果 # 建数组 listOfNum = [0]*(digit+1) # 装每一次相乘的结果,预设的数组多一位,防止越界 # 数组开始应为1 listOfNum[0] = 1 # 开始正式的乘法,让前一位数,乘以新的乘数 for num_j in range(2, num+1): # 确定目前的最高位 for k in range(digit, -1, -1): # 找最高位 if listOfNum[k] != 0: highBit = k # 最高位所在的索引 break for i in range(highBit+1): # 已知最高位的索引后,从第一个数开始乘乘数 listOfNum[i] *= num_j # 相乘的结果,还是直接存在数组里,先处理进位 # 等所有为乘完,才处理进位 carryHandle(listOfNum, highBit) # 等大for循环结束,结果也就出来了 # 打印结果 # 确定最高位,方便打印 for k in range(digit, -1, -1): if listOfNum[k] != 0: highBit = k break print('拆解算法:', num, '! = ', end='') for i in range(highBit, -1, -1): print(listOfNum[i], end='') print()
第三个输出~这里的输出,是把数组从最高位输出到最低位,跟第一、二种方法直接数字输出,是有本质不同的。
def carryHandle(listOfNum, numOfBit):
,这个函数最为关键,输入储存乘积结果的数组和此时数字的位数for i in range(1, num+1):
sumOfBits += math.log10(i) # log2则是求比特的位数,因为是10进制,所以对10求log;此时是实数
digit = int(sumOfBits)+1 # 最长的结果
对10求 log, 是因为我们这里是10进制,用二进制求位数的方法理解这里即可。同时,思考为什么这里是加~确实很巧妙
5. 关于Python 3 print() 函数的小知识。在Python 3中,print() 已经变成了一个语句,默认输出是换行,而我们这里需要的,恰恰就是不换行。于是,在print()函数的参数中,增加 end=’’, 这样输出一个数字后,不会有空格或是换行,而是接着输出。原函数缺省的默认参数是 end=’\n’,这就不难理解为什么每次输出后,都是自动换行了。
(print()输出的控制,参考了这篇文章,很有帮助,非常感谢~ 传送门)
对于方法三,采用数组来表达一个大数,何尝不是又一次空间策略的精彩应用呢~ 这里体现了化整为零的思想。虽然Python 3 已经内置了处理非常大数相乘的逻辑,可是,掌握背后的思想,真的挺有趣的,当然,方法三运行肯定很慢,只是,它设计的初衷在于能算而不是为了快啊,理论上只有计算机内存允许,多大的数,都能算。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。