问题
给定三个正整数 a、b、m( a<109,b<106,1<m<109),求 ab % m 的值
一般写法
为了防止溢出使用long,Java代码,long为64位,等同于 C语言 的 long long
public long binaryPow(long a, long b, long m){
long ans = 1;
for(int i = 0; i < b; i++){
ans = ans * a % m;
}
return ans;
}
更进一步问题
给定三个正整数 a、b、m( a<109,b<1018,1<m<109),求 ab % m 的值
b的范围扩大到 1018,上面的方法肯定不行,O(b)的复杂度难以支持 1018。
所以使用快速幂的方法
快速幂
基于二分的思想,也叫“二分幂”:
① 如果 b 是奇数,那么 ab = a * ab-1
② 如果 b 是偶数,那么 ab = ab/2 * ab/2
递归写法
// 求 a^b%10,递归写法
long binaryPow(long a, long b, long m ){
if(b == 0) return 1; // 如果 b 为 0,那么 a^0=1;
// b 为奇数,转化为b-1
if(b & 1 == 1)
return a * binaryPow(a, b-1, m) % m;
else{ // b 为偶数,转换为 b/2
long mul = binaryPow(a, b/2, m) % m;
return mul * mul % m;
}
}
-
判断奇数可以使用 b & 1 == 1,也可以 b % 2 == 1
-
b为偶数时,不要
(binaryPow(a, b/2, m) % m)* binaryPow(a, b/2, m) % m
,因为这样会计算两个递归 -
细节
- 如果 a 的初始值有可能大于等于 m,那么需要再进入函数前让其对 m 取模
- 如果 m 为 1,可以直接再函数外特判返回 0,因为任何正整数对 1 取模一定为 0
迭代写法
如果把 b 写成 二进制,就可以把 ab 表示为 a 的二次幂的和。
例如:当 b = 13,就是1101,13 = 1 * 23 + 1 * 22 + 0 * 21 + 1 * 20,也就表示成 a13 = a8 + a4 + a1,前一项总是等于后一项的平方(有些位是乘0)
因此具体实现的时候可以这么做:
- 初始令 ans 等于 1,用来存放累计的结果
- 判断 b 的二进制末尾是否为 1(即判断 b 是否为奇数,b&1 是否为 1),如果是,令 ans 乘上 a
- 令 a 平方,并将 b 右移一位(也可理解为将 b 除 2)
- 只要 b 大于0,就回到第2步
// 求 a^b%10,迭代写法
long binaryPow(long a, long b, long m){
long ans = 1;
while(b > 0){
if(b & 1 == 1){
ans = ans * a % m;
}
a = a * a % m;
b >>= 1;
}
return ans;
}
实际中递归写法和迭代写法效率差距不明显,所以使用两种都可
参考:《算法笔记》胡凡