在众多的加密算法中都需要进行幂的取模运算,比如在RSA算法中需要计算ne mod N,我们称之为幂模算法。
我经常第一个想到的算法就是用pow(),最后发现根本得不到想要的结果,测试才方向是因为溢出。
当指数很大时我们采用两种方法。
方法一
利用性质:a*b%m=a*(b%m)%m
a^b%m=(a%m)^b%m
int mod(int a,int b,int m){
int result = 1;
for(int i=0;i<b;i++) {
result = (result*a) %m;
}
return result;
}
但当b很大时这个方法循环的次数还是相对太大,不是很理想。
方法二
进一步研究指数b的二进制表示发现,对任意的整数b都可表示为:
- n表示b的实际二进制位数
- bi表示该位是0或1
因此,ab可表示为:
即用b的每一位表示a的每一项,而对任意相邻的两项存在平方关系,即:
因此我们构造下面的算法:
- 把b转换为二进制表示,并从右至左扫描其每一位(从低到高)
- 当扫描到第i位时,根据计算a的第i项的模,这里先是假设多有的bi都是1,记录下来,当真正的bi为1时供result使用
base变量表示第i-1位时计算出的模。
- 如果第i位为1,即bi=1,则表示该位需要参与模运算,计算结果 result = (result*base) mod m;其中result为前i-1次的计算结果。
int mod(int a,int b,int m){
int result = 1;
int base = a;
while(b>0){
if(b & 1==1){//与1按位与
result = (result*base) % m;
}
base = (base*base) %m;//难点在这
b>>=1;//相当于除2
}
return result;
}
把时间复杂度降到了lgb,大大优于方法一。
其实还需要考虑m为负数的情况
leetcode
50. Pow(x, n)
Implement pow(x, n).
为什么new要用unsigned long long?因为n可能是INT_MIN
class Solution {
public:
double myPow(double x, int n) {
double res = 1;
unsigned int newn = n;
if (n < 0) {
x = 1 / x;
newn = -n;
}
while (newn){
if (newn & 1){
res *= x;
}
x *= x;
newn >>= 1;
}
return res;
}
};