给定三个正整数 a, b, m(a < 109,b < 109,1 < m < 109),求 ab % m。
对于这个问题,只要写一个简单的循环就能够搞定
1 2 3 4 5 6 7 8
// 普通求幂 longlongQuickPow(longlong a, longlong b, longlong m){ longlong ans = 1; for (int i = 0; i < b; i++) { ans = ans * a % m; } return ans; }
然而,当 a, b 到达一定值时,最终的结果会非常大,对于这个问题,O(b)的时间复杂度很难进行。
快速幂算法
快速幂,就是用效率更高(时间复杂度更低)的方法求幂,可以将时间复杂度优化至 O(logn)
递归快速幂
快速幂算法的关键在于对指数 b 的处理,我们很容易得到如下事实:
若 b 为奇数,则ab=a×ab−1
若 b 为偶数,则ab=ab/2×ab/2
举个例子,求 27:
由于 7 为奇数,因此有 27=2×26
由于 6 为偶数,因此有 26 = 23×23
由于 3 为奇数,因此有 23 = 2×22
由于 2 为偶数,因此有 22 = 21×21
由于 1 为奇数,因此有 21 = 2×20
最后 20=1,然后从下往上计算即可
根据上面的方程,很容易通过二分的思想得到快速幂算法的递归版本
1 2 3 4 5 6 7 8 9 10
// 快速幂,递归写法 longlongQuickPow(longlong a, longlong b, longlong m){ if (b == 0) return1; if (b % 2 == 1) // 如果 b 为奇数,转化为 b - 1 return a * QuickPow(a, b - 1, m) % m; else { // 如果 b 为偶数,转化为 b / 2 longlong mul = QuickPow(a, b / 2, m); return mul * mul % m; } }
迭代快速幂
下面说明一下快速幂的迭代写法
同样的,主要还是在指数 b 的处理,以 210 为例
10=(1010)2=1×23+0×22+1×21+0×20
210=21×23+0×22+1×21+0×20=28+0+2+0=28×22
可以发现,210 可以表示为 28 和 22 的乘积。
同样的对 ab 进行推广,我们可以把 ab 表示成 a2k,a2k−1,…,a8,a4,a2,a1中若干项的乘积,即:
// 快速幂,迭代写法 longlongQuickPow(longlong a, longlong b, longlong m){ longlong ans = 1; while (b > 0) { if (b & 1) { // 若 b 的二进制末尾为 1(也可以写成 if(b % 2)) ans = ans * a % m; // ans 累加上 a } a = a * a % m; // a取平方 b >>= 1; // b 的二进制右移一位(也可以写成 b /= 2) } return ans; }