思路
你应该很早就注意到一个问题:依靠朴素循环的线性级别的指数运算是不必要的。
例如,
pow(3,4)=3*3*3*3
↓
pow(3,4)=9*9
这样循环的工作量明显大幅下降了。
*注意*: 与少量循环的大值相乘相比,大量循环的小值相乘要慢得多。
我们今天就来研究这个问题。
1.快速幂
算法过程
快速幂运算就是依靠上述思路进行的算法,但我们要着重探讨关于指数的问题。
那么按照思路,快速幂的过程就应该是
int ans=1;
每次循环中应该是ans*=如下数
pow(x,1) → pow(x,2) → pow(x,4) → pow(x,8)
但是观察:
- ------- ---------------
pow(x,13)= x * x*x*x*x * x*x*x*x*x*x*x*x
= pow(x,1) * pow(x,4) * pow(x,8)
嘶,我们怎么就知道可以省略pow(x,2)才能凑整呢?
这时候一个精妙之处就来了:13转二进制位是1101。
(8 4 2 1)
而1101的位权从左到右分别对应2^3,2^2,2^1,2^0。
1 1 0 1
即13=1*2^3+1*2^2+0*2^1+1*2^0。
观察二进制位我们发现:
将n转为二进制位时,0的位置被忽略,因此省略pow(x,2)才能凑整。
来看Code。
Code
int quick_pow(int x,int n){
int ans=1;
while(n){
if(n&1)ans*=x;//如果n的末尾是1,那么不能忽略现在的x
x*=x; //x指数翻倍
n>>=1; //n右移一位,1101→110,那么下次循环时忽略,即忽略pow(x,2)
}
}
2.快速幂取模
算法过程
那么我们怎么计算pow(x,n)%m呢?(即 pow(x,n) mod m)
首先,你至少要知道:
a * b % m = ((a % m) * (b % m)) % m
即相乘后取模=分别取模后再相乘最后再取模。
那么,我们的过程就应该是:
pow(x,13)%m = ({pow(x,5)%m}*{pow(x,8)%m})%m
------------
↓
= ({[(pow(x,1)%m)*(pow(x,4)%m)]%m}*pow(x,8)%m)%m
-------------------------------
= (((1%m*(x^1)%m)%m*(x^4)%m)%m*(x^8)%m)%m
--第一次循环----
-------第三次循环-----------
------------第四次循环------------------
那我们只需要将Code稍加改动即可。
Code
int quick_pow_mod(int x,int n,int m){
int ans=1;
x=x%m;
//ans=ans%m可以忽略
while(n){
if(n&1)ans=(ans*x)%m;//这里ans和x已经在上一次循环对m取模了
x=(x*x)%m; //pow(x,k)%m
n>>=1; //n右移一位
}
}
复杂度
时间复杂度: O(logn)
空间复杂度: O(1)