二分:快速冪

問題

給定三個正整數 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;
    }
}
  1. 判斷奇數可以使用 b & 1 == 1,也可以 b % 2 == 1

  2. b爲偶數時,不要(binaryPow(a, b/2, m) % m)* binaryPow(a, b/2, m) % m,因爲這樣會計算兩個遞歸

  3. 細節

    • 如果 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)

因此具體實現的時候可以這麼做:

  1. 初始令 ans 等於 1,用來存放累計的結果
  2. 判斷 b 的二進制末尾是否爲 1(即判斷 b 是否爲奇數,b&1 是否爲 1),如果是,令 ans 乘上 a
  3. 令 a 平方,並將 b 右移一位(也可理解爲將 b 除 2)
  4. 只要 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;
}

實際中遞歸寫法和迭代寫法效率差距不明顯,所以使用兩種都可


參考:《算法筆記》胡凡
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章