快速幂取模算法

Posted 王陸

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速幂取模算法相关的知识,希望对你有一定的参考价值。

1.大数模幂运算的缺陷:

快速幂取模算法的引入是从大数的小数取模的朴素算法的局限性所提出的,在朴素的方法中我们计算一个数比如5^1003%31是非常消耗我们的计算资源的,在整个计算过程中最麻烦的就是我们的5^1003这个过程
缺点1:在我们在之后计算指数的过程中,计算的数字不都拿得增大,非常的占用我们的计算资源(主要是时间,还有空间)
缺点2:我们计算的中间过程数字大的恐怖,我们现有的计算机是没有办法记录这么长的数据的,所以说我们必须要想一个更加高效的方法来解决这个问题

2.快速幂的引入:

我们首先从优化的过程开始一步一步优化我们的模幂算法

1.朴素模幂运算过程:

1 #define ans=1
2 for(int i=1;i<=b;i++)
3 {
4     ans*=a;
5 }
根据我们上面说的,这种算法是非常的无法容忍的,我们在计算的过程中出现的两个缺点在这里都有体现
在这里我们如果要做优化的话,我肥就是每个过程中都加一次模运算,但是我们首先要记住模运算是非常的消耗内存资源的,在计算的次数非常的大的时候,我们是没有办法忍受这种时间耗费的

2.快速幂引入:

在讲解快速幂取模算法之前,我们先将几个必备的知识
1.对于取模运算:
 1 (a*b)%c=(a%c)*(b%c)%c 
这个是成立的:也是我们实现快速幂的基础
之后我们来看看快速幂的核心本质
我通过离散课上的学习,将快速幂的本质差不多理解了一下,感觉还是很深刻的
 
在这里,我们对指数动了一些手脚,核心思想在于
大数的幂运算拆解成了相对应的乘法运算,利用上面的式子,始终将我们的运算的数据量控制在c的范围以下,这样我们可以客服朴素的算法的缺点,我们将计算的数据量压缩了很大一部分,当指数非常大的时候这个优化是更加显著的,我们用Python来做一个实验来看看就知道我们优化的效率有多高了
 
 1 from time import *
 2 def orginal_algorithm(a,b,c):  #a^b%c
 3     ans=1
 4     a=a%c  #预处理,防止出现a比c大的情况
 5     for i in range(b):
 6         ans=(ans*a)%c
 7     return ans
 8 
 9 def quick_algorithm(a,b,c):
10     a=a%c
11     ans=1
12     #这里我们不需要考虑b<0,因为分数没有取模运算
13     while b!=0:
14         if b&1:
15             ans=(ans*a)%c
16         b>>=1
17         a=(a*a)%c
18     return ans
19 
20 time=clock()
21 a=eval(input("底数:"))
22 b=eval(input("指数:"))
23 c=eval(input("模:"))
24 print("朴素算法结果%d"%(orginal_algorithm(a,b,c)))
25 print("朴素算法耗时:%f"%(clock()-time))
26 time=clock()
27 print("快速幂算法结果%d"%(quick_algorithm(a,b,c)))
28 print("快速幂算法耗时:%f"%(clock()-time))

 

 
技术分享图片

 

我们现在知道了快速幂取模算法的强大了,我们现在来看核心原理:

 

对于任何一个整数的模幂运算
a^b%c

对于b我们可以拆成二进制的形式
b=b0+b1*2+b2*2^2+...+bn*2^n
这里我们的b0对应的是b二进制的第一位

那么我们的a^b运算就可以拆解成
a^b0*a^b1*2*1...*a^(bn*2^n)
对于b来说,二进制位不是0就是1,那么对于bx为0的项我们的计算结果是1就不用考虑了,我们真正想要的其实是b的非0二进制位

那么假设除去了b的0的二进制位之后我们得到的式子是
a^(bx*2^x)*...*a(bn*2^n)
这里我们再应用我们一开始提到的公式,那么我们的a^b%c运算就可以转化为

(a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)
这样的话,我们就很接近快速幂的本质了

(a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)
我们会发现令
A1=(a^(bx*2^x)%c)
...
An=(a^(bn*2^n)%c)
这样的话,An始终是A(n-1)的平方倍(当然加进去了取模匀速那),依次递推

 

现在,我们基本的内容都已经了解到了,现在我们来考虑实现它:

 1 int fast_pow(int a,int b,int c)
 2 {
 3     int ans=1;   ///记录结果
 4     a=a%c;   ///预处理,使得a处于c的数据范围之下
 5     while(b!=0)
 6     {
 7         if(b&1) 
 8         {
 9             ans=(ans*a)%c;   ///如果b的二进制位不是0,那么我们的结果是要参与运算的
10         }
11         b>>=1;    ///二进制的移位操作,不断的遍历b的二进制位
12         a=(a*a)%c;   ///不断的加倍
13     }
14     return ans;
15 }

 

在这里还要进行几点说明:

1.二进制的几个运算符&  和 >> 。

  &运算通常用于二进制取位操作,例如一个数 & 1 的结果就是取二进制的最末位。还可以判断奇偶,x&1==0为偶,x&1==1为奇。

   >>运算比较单纯,二进制去掉最后一位,移位操作,不断遍历b的二进制位。

2. a=(a*a)%c这一步的作用是用来不断的加倍,在先不看同余定理的情况下,a*a==a^2,下一步再乘,就是a^2*a^2==a^4,然后同理  a^4 * a4 =a^8 .........?是不是做到了

    a-->a^2-->a^4-->a^8-->a^16-->a^32.......指数正是 2^i 啊,再看上面的例子,a¹¹ =  a^(2^0) * a^(2^1) * a^(2^3),这三项是不是完美解决了,快速幂就是这样。

 

以上是关于快速幂取模算法的主要内容,如果未能解决你的问题,请参考以下文章

快速幂取模算法

Raising Modulo Numbers_快速幂取模算法

关于快速幂取模

快速幂和快速幂取模

ACwing89 a^b 快速幂取模

牛客-开心的涂刷——数学分析+快速幂取模问题