二分:快速幂

问题

给定三个正整数 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;
}

实际中递归写法和迭代写法效率差距不明显,所以使用两种都可


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