学习记录 快速幂
快速幂的递归实现
假设要算(7^9),如果采取普通计算,也就是(7*7*7*7*7*7*7*7*7),共需要8次运算。
运用二分的思想,先算(7^4),然后通过(7^4*7^4*7)来计算$7^9 $,这样就只需要(3+1+1=6)次计算,然而这样还不够彻底,(7^4)还可以通过分解成(7^2*7^2)的形式,这样递归下去,就得到了时间复杂度为(O(logn))的快速幂算法。
int Quickpow(int a,int n)
{
if (n==0)
return 1;
else if (n%2==1)
return Quickpow(a,n-1)*a;
else{
int temp=Quickpow(a,n/2);//必须先保存下来,否则会算两遍
return temp*temp;
}
}
做题的时候,幂的结果可能会非常大,需要对一个大数取余,这时将上面的函数改成long long
,在运算的每一步都要取余,结果也要取余啊!改进代码如下:
const int MOD=1e9+7;
typedef long long ll;
ll Quickpow(ll a,ll n)
{
if (n==0)
return 1;
else if (n%2==1)
return Quickpow(a,n-1)*a%MOD;
else{
ll temp=Quickpow(a,n/2)%MOD;
return temp*temp%MOD;
}
}//可以过洛谷P1226,就是结果也必须取余
非递归实现
非递归实现主要用了二进制的思想。
在上面的递归实现中不难发现是每次都是将结果分割为两半,这正好对应了二进制。而且众所周知,位运算是比乘法运算快的,运用非递归实现因此比递归实现快一点。
还是(7^9)的例子,(9)的二进制形式为(1001),意味着(7^{(1001)_2})可以拆分为(7^{(1000)_2}*7^{(1)_2}),以此类推,任何幂总可以拆分成(a^{2^b})相乘的形式,因此就有了思路。
- 总体思路就是从二进制最后一位开始算起,如果在这一位的二进制数为1,则说明需要加上这一块;若是0则跳过。然后到下一位。不过在此用位运算的右移实现。
- 计算(a^{2^b}),可以用底数自乘来实现,在每次运算中代表在这一位的(7^{2^{x}})是多少。
- 判断二进制的情况,需要用一些位运算的基本知识。
非递归计算(7^9)的过程:
临时变量 | 指数情况(二进制) | 目前的运算结果 |
---|---|---|
7((7^{2^0 })) | 1001 | 1*7=7 |
49((7^{2^1})) | 0100 | 7 |
2401((7^{2^2})) | 0010 | 7 |
5764801((7^{2^3})) | 0001 | 7*5764801=40353607 |
代码实现:
int Quickpow(int a,int n)
{
int res=1;
while (n){ //最终右移的结果为0
if (n&1) //指数二进制末尾为1
res*=a; //乘以当前的底数
a*=a; //底数自乘
n>>=1; //指数右移一位
}
return res;
}