快速幂
快速幂以及拓展的矩阵快速幂,由于应用场景比较常见,也是竞赛中常见的题型。
幂运算 an 即 n 个 a 相乘。快速幂就是高效的算出 an 。当 n 很大时,例如 n = 109 ,计算这样大的数 Java 也不能处理,一是数字太大,二是计算时间很长。下面先考虑如何缩短计算时间,如果用暴力的方法直接算 an ,即逐个做乘法,复杂度是 O(n),即使能算出来,也会超时。
读者很容易想到快速幂的办法:先算 a2 ,然后继续算平方 (a2)2 ,一直算到 n 次幂。这就是分冶法的思想,复杂度为 O( log2 n )。下面的代码,请读者自己理解:
//分冶法
int fastPow(int a,int n){
if(n == 1) return a;
int temp = fastPow(a,n/2); //分冶
if(n%2 == 1) //奇数个 a ,此处也可以写为 if(n & 1)
return temp * temp * a;
else //偶数个 a
return temp * temp;
}
程序中的递归,层数只有 log2 n ,不用担心溢出的问题。
上面的程序非常好,不过还有一种更好的办法,是用位运算做快速幂,时间复杂度也是O( log2 n )。下面以 a11 为例说明快速幂的原理。
先把 a11 分解成 a8、 a2、 a1 的乘积,即 a11 = a8 * a2 *a1。
如何求a8、 a2、 a1 的值,需要分别计算吗?并不需要。用户可以容易地发现, a1 * a1 = a2,a2 * a2 = a4,a4 * a4 = a8,等等,都是 2 的倍数,产生的 ai 都是倍乘关系,逐级地推就可以了。在下面的程序中,这个功能用 base = base * base; 实现。
那么如何把 n 分解成 11 = 8 + 2 + 1 这样的倍乘关系?用二进制就能理解了。把 n 转为二进制数,二进制数中每一位的权值都是第一位的两倍,对应的 ai 是倍乘的关系,例如 n = 1110 = 10112 = 23 + 21 + 20 = 8 + 2 + 1,所以只需要把 n 按二进制处理就可以了。
另外还有一个需要处理的问题:如何跳过那些不需要的?例如求 a11 ,因为 11 = 8 + 2 + 1,需要跳过 a4 。这个做个判断即可,1011 中的 0 就是需要跳过的。这个判断,利用二进制的位运算很容易实现:
(1) n & 1,取 n 的最后一位,并且判断这一位是否需要调过。
(2) n >>= 1,把 n 右移一位,目的是把刚处理过的 n 的最后一位去掉。
//位运算
int fastPow(int a,int n){
int base = a; //不定义 base,直接用 a 进行计算也行
int res = 1; //用 res 返回结果
while(n){
if(n & 1) //如果 n 的最后一位是 1,表示这个地方需要乘
res *= base;
base *= base; //推算乘积,a^2 --> a^4 --> a^9 --> a^16 ...
n >>= 1; // n 右移一位,把刚处理过的 n 的最后一位去掉
}
return res;
}
对照上面的程序,执行步骤如下表所示。
n | res(res *= base) | base(base *= base) | |
---|---|---|---|
第一轮 | 1011 | a^1 | a^2 |
第二轮 | 101 | a ^ 1 * a ^ 2 | a^4 |
第三轮 | 10 | 是0,res不变 | a^8 |
第四轮 | 1 | a ^ 1 * a ^ 2 * a ^ 8 | a^16 |
结束 | 0 | – | – |