問題
給定三個正整數 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;
}
實際中遞歸寫法和迭代寫法效率差距不明顯,所以使用兩種都可
參考:《算法筆記》胡凡