Python实现快速幂取余算法

引子

今天无意中,看到了慧科教育科技集团有限公司-后厂理工学院 的AI相关学员招募信息,要求颇高,还要做编程自测题,并且达到60分才建议进行课程的学习。我忍不住发送了报名信息,得到了自测题。看到了如下的一道题目:
image.png


快速幂取余算法实现

第一眼,看到题目,因为之前没有接触过快速幂算法,我心想,这也太简单了~~,于是分分钟做出了第一版的程序:

import math


def count_factorial(x):
    result = 1
    for each in range(1, x + 1):
        result *= each
    return result


def function(a, b, c):
    return math.pow(a, count_factorial(b)) % c


print(function(2, 1, 2), 0)
print(function(3, 2, 2), 1)
print(function(1, 100000, 1), 0)

然后,报错了。

0.0 0
1.0 1
Traceback (most recent call last):
  File "D:/code/test2.py", line 17, in <module>
    print(function(1, 100000, 1), 0)
  File "D:/code/test2.py", line 12, in function
    return math.pow(a, count_factorial(b)) % c
OverflowError: int too large to convert to float

这时我才意识到,原来这道题的重点,不是在于数学运算功能的实现,而是重点在于大数的运算。
因此,这是一倒算法题,
f ( a , b , c ) = a b ! M O D c f(a, b, c) = a^{b!} MODc f(a,b,c)=ab!MODc
算法的核心就是如何将上式的a 和 b!不断的减小,以免超出python定义的整形的最大长度。
PS: 公式中的 MOD 就是数学中取余的运算符 也就是程序中我们常用的 %

  1. 减小公式中a的方法

迅速百度查到如下已经被证明的定理:
积的取余等于每个数取余的乘积再取余
意思就是说, f ( a , b , c ) = a b ! M O D c = ( a M O D c ) b ! M O D c f(a, b, c) = a^{b!} MODc= (a MODc)^{b!}MODc f(a,b,c)=ab!MODc=(aMODc)b!MODc
举个例子7^8%2 = (7%2)^8%2 = 1^8%2 = 1%2 = 1
这个数学定理有效的帮助我们减小了a 的值。

但是这还是远远不够!!因为b!(b的阶乘)会是一个更大的挑战。
2. 减小公式中b的方法(我们将b!作为一个很大的数用b来代替)
我们可以通过快速幂的方式来进行计算:

我们都知道指数的运算法则, a x + y = a x ∗ a y a^{x+y} = a^{x}*a^{y} ax+y=axay 那么公式中的b!也可以通过拆分达到减小b的效果。
当b为偶数时: a b = a b / 2 ∗ a b / 2 = a b / 2 ∗ 2 = ( a 2 ) b / 2 a^{b} = a^{b/2}*a^{b/2}=a^{b/2*2}=(a^2)^{b/2} ab=ab/2ab/2=ab/22=(a2)b/2
当b为奇数时: a b = a ∗ a b − 1 = a ∗ a ( b − 1 ) / 2 ∗ a ( b − 1 ) / 2 = a ∗ a ( b − 1 ) / 2 ∗ 2 = a ∗ ( a 2 ) ( b − 1 ) / 2 a^{b} = a*a^{b-1}=a*a^{(b-1)/2}*a^{(b-1)/2}=a*a^{(b-1)/2*2}=a*(a^2)^{(b-1)/2} ab=aab1=aa(b1)/2a(b1)/2=aa(b1)/22=a(a2)(b1)/2

注意: 我们发现无论是奇数还是偶数,最后我们都会更新a为a^2并且将 b更新为b/2,只不过当b为奇数时先将b更新为b-1再将b更新为b/2

因此python实现的代码如下

# 计算输入的阶乘
def count_factorial(y):
    result = 1
    for each in range(1, y + 1):
        result *= each
    return result


def function(a, b, c):
    b = count_factorial(b)
    result = 1
    # 减小a
    a = a % c

    while b > 0:
        # 当b为奇数时
        if b % 2 == 1:
            result = (result * a) % c
            b -= 1
        b = b / 2
        # 不断的减小a
        a = (a ** 2) % c
    return result


print(function(2, 1, 2), 0)
print(function(3, 2, 2), 1)
print(function(1, 100000, 1), 0)
print(function(1, 100000, 2), 1)
print(function(99036, 92879, 77028), 0)
print(function(14916, 63624, 37968), 32544)
print(function(48778, 6070, 89146), 57188)

输出结果为:

D:\code\venv\Scripts\python.exe D:/code/test2.py
0 0
1 1
Traceback (most recent call last):
  File "D:/code/test2.py", line 29, in <module>
    print(function(1, 100000, 1), 0)
  File "D:/code/test2.py", line 21, in function
    b = b / 2
OverflowError: integer division result too large for a float

Process finished with exit code 1

原因是:

在python3中两个数做除法运算,无论除数和被除数是否为float型 结果都会被强制的转换为float,但是b是一个很大的数,虽然可以被python自己转换的长整形保存,但是他的一半依然很大无法被float型保存。因此我们无法使用除法进行计算。

解决方法:

使用位运算来做除法的运算。
PS:我们要注意位运算的除法相当于整除,不会保留余数。
举个例子:
2>>1 的结果为 1 但是 3>>1 的结果同样为 1 原因是3的二进制位11向右移一位变为二进制的1 也就是十进制的1。
也就是说,位运算帮助了我们做了当b为奇数时b=b-1的操作。

因此,我们的python代码如下:

# 计算输入的阶乘
def count_factorial(y):
    result = 1
    for each in range(1, y + 1):
        result *= each
    return result


def function(a, b, c):
    b = count_factorial(b)
    result = 1
    # 减小a
    a = a % c

    while b > 0:
        # 当b为奇数时
        if b % 2 == 1:
            result = (result * a) % c
        # b更新为b/2
        b = b >> 1
        # a更新为a^2 并 取余减小a的值
        a = (a ** 2) % c
    return result


print(function(2, 1, 2), 0)
print(function(3, 2, 2), 1)
print(function(1, 100000, 1), 0)
print(function(1, 100000, 2), 1)
print(function(99036, 92879, 77028), 0)
print(function(14916, 63624, 37968), 32544)
print(function(48778, 6070, 89146), 57188)

###算法优化
现在算法没有什么问题了,但是我们发现还存在很多可以优化的提高,来提高算法的效率。

f ( a , b , c ) = a b ! M O D c f(a, b, c) = a^{b!} MODc f(a,b,c)=ab!MODc

  1. 当a变为0之后,整个运算的答案也就为0
  2. 当a变为1之后,无论通过a = a^2 来更新a,a都为1,并且只要c的值不为1 那么(a^2)%c 依然为1,当c的值为1时,a会变为0走第一条所述的流程结果直接为0

通过优化算法得到python程序如下:

# 计算输入的阶乘
def count_factorial(y):
    result = 1
    for each in range(1, y + 1):
        result *= each
    return result


def function(a, b, c):
    b = count_factorial(b)
    result = 1
    # 减小a
    a = a % c

    while b > 0:
        # 当b为奇数时
        if b % 2 == 1:
            result = (result * a) % c
        # b更新为b/2
        b = b >> 1
        # a更新为a^2 并 取余减小a的值
        a = (a ** 2) % c
        if a == 0:
            return 0
        if a == 1:
            return result
    return result


print(function(2, 1, 2), 0)
print(function(3, 2, 2), 1)
print(function(1, 100000, 1), 0)
print(function(1, 100000, 2), 1)
print(function(99036, 92879, 77028), 0)
print(function(14916, 63624, 37968), 32544)
print(function(48778, 6070, 89146), 57188)

PS:这一条 function(14916, 63624, 37968) 由于数量级很大,运算既然很慢可能需要等几分钟。

运行结果如下:

D:\code\venv\Scripts\python.exe D:/code/test2.py
0 0
1 1
0 0
1 1
0 0
32544 32544
57188 57188

Process finished with exit code 0

##后记
这是我在第一次写技术博客,希望我可以坚持下来不断的积累,得到成长。欢迎各位留言交流。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值