学习记录:快速幂
学习记录 快速幂
快速幂的递归实现
假设要算\(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;
}