快速幂 详解

快速幂

快速幂以及拓展的矩阵快速幂,由于应用场景比较常见,也是竞赛中常见的题型。

幂运算 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;
}

对照上面的程序,执行步骤如下表所示。

nres(res *= base)base(base *= base)
第一轮1011a^1a^2
第二轮101a ^ 1 * a ^ 2a^4
第三轮10是0,res不变a^8
第四轮1a ^ 1 * a ^ 2 * a ^ 8a^16
结束0
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值