学习参考自:https://www.cnblogs.com/wendelhuang/p/3414738.html
https://blog.csdn.net/u011590573/article/details/81457797
https://blog.csdn.net/wzb56_earl/article/details/7199343
对于快速幂求解问题不是很了解,所以特地学习下,记下自己的理解,当然可能理解有错误,欢迎指出来 (:逃 毕竟我也刚学会
快速幂
快速幂的目的就是做到快速求幂,假设我们要求a^b,按照朴素算法就是把a连乘b次,这样一来时间复杂度是O(b)也即是O(n)级别,快速幂能做到O(logn)
第一种实现代码是这个样子的:(一般网上的代码)右->左
int poww (int a , int b ) {
int temp_result = 1, temp_base;
while (b != 0) {//判断是否终止
if (b & 1 != 0) //判断二进制最后一位 0 1
temp_result *= temp_base;
temp_base *= temp_base; //自增时刻准备着 也是之所以称之为反复平方法的缘由。
b >>= 1; //右移操作
}
return temp_result ;
}
我对于这种快速幂的理解如下:
求a^b,因为b一定可以被化为二进制数,假设求3^13,那么13可以被化为1101,1101可以拆成(1000+100+【00】+1)
二进制1000=2^3=8,100=2^2=4,1=2^0=1,现在我们可以理解为求3^13=3^1*【3^0】*3^4*3^8,显然只要4步就可以求出结果
其中加快的部分就是在3^4是temp_base所保存的临时值,到了下一个3^8的时候会利用temp_base*=temp_base得到
temp_base= a ,a^2,a^4,a^8,a^16 的递增
对应本题中 [ 1101 ] 1 ( 2^3 ) 1( 2^2 ) 0( 2^1 ) 1 ( 2^0 )
temp_base a ^ 8 a ^ 4 a ^ 2 a ^ 1
遇到 0 ,temp_result*=temp_base 这不就不做,遇到 1,就做 temp_result*=temp_base
注: 这步含义就是二进制中的加法 [ a^ (8+4+1) ]对应实际操作的乘法[ a^8*a^4*a^1 ]
注: a^8=a^(2^3)=a^(2^2)*a^(2^2) 说明b的二进制 100和1000的关系 a^4和a^8
举例a^13说明快速幂求解过程:
初始化temp_result=1,temp_base=a;
Step1:从右向左看1101,利用if(b&1)来判断,因为式子成立说明b的最后一位是1,即做temp_result*=temp_base(数值a)
然后 temp_base*=temp_base(数值a^2),为第二次执行temp_result做准备。然后利用b>>1 ,将1101变成110
Step2:从右向左看110,if(b&1)判断式子最后一位为0,所以temp_result还是a,temp_base*=temp_base(数值a^4)
b>>1 ,将110变成11
Step3:从右向左看11,if(b&1)判断式子最后一位为1,temp_result*=temp_base(数值为a^5=a*a^4),temp_base自乘数值变 为a^8, b>>1 ,将11变成1
Step4:从右向左看11,if(b&1)判断式子最后一位为1,temp_result*=temp_base(数值为a^13=a*a^4*a^8),temp_base自乘 值变为a^16, b>>1 ,将1变成0
Output:temp_result = a ^ 13 [ 这样操作后发现,就是遍历二进制数是否为01,次数和二进制数位数有关 ]
(temp_base不管值是否为01,,每次都要temp_base*=temp_base,时刻准备下一次被temp_result用到)
第二种实现是《算法导论》的实现,代码是这个样子的:左->右
int poww(int a, int b) {
int temp_result = 1;
for (int i = 31; i>=0; i--) {
temp_result *= temp_result; //此处就是反复平方法的名字由来
if (b & 1<<i)
temp_result *= a;
}
return temp_result;
}
我对这个代码的理解: a^1 , a^11 , a^110 , a^1101 方向从左向右看
首先 int b , 4字节 32位,去除最高位符号位,剩下31位,进行32次操作,噗~= =~肯定不会出现漏位数,But 多了啊 (: 解释不来
算导的方向和我们上面的不一样,它是从左向右看,其中需要理解的一句是 if (b & 1<<i) 这句,其实加个括号优先级可能就会
好理解了 if (b &(1<<i))假设 i =2,用 1101 &(0100)判断此时的第 i 位是 0 还是 1,好了我们知道这句含义来看整段代码。
还是举例子a^13来说:
初始化temp_result=1
其中 1101前面全是0,所有temp_result不管怎么反复平方还是1
红色代表 i 的位置 蓝色代表还没判断的部分
注:i 在原二进制数中是第 i 位,但是要当最低位看【计算的时候其实它就是最低位】黑色部分和红色部分,红色确实最低
所以才会在 i 位为1时出现 temp_result *= a 只有最低位是2^0,即a(2^0)=a
蓝色部分的值不管因为它还没有出现,黑色部分的值也不管因为他已经判断结束了
至于反复平方原因就是,1101看的方向是左到右,1到11,二进制已经升了1位,例如:100(4)->1000(8)升了一位,二进制是2倍的差距,但是a^4和a^8是自乘的差距,这就是原因。所以每次 i 移动都代表了升位的产生,所有要反复平方
< 以上都是我的个人理解可能有错误 >
Step1: 此时二进制 1101,temp_result*=temp_result 自身平方一次,temp_result值为1, if (b & 1<<i)判断为1
所以temp_result *= a,temp_result值为a( 1*a )
Step2:此时二进制 1101,temp_result*=temp_result 自身平方一次,temp_result值为a^2, if (b & 1<<i)判断为1
所以temp_result *= a,temp_result值为a^3( a^2*a )
Step3:此时二进制 1101,temp_result*=temp_result 自身平方一次,temp_result值为a^6, if (b & 1<<i)判断为0
所以temp_result值不变.
Step4:此时二进制 1101,temp_result*=temp_result 自身平方一次,temp_result值为a^12, if (b & 1<<i)判断为1
所以temp_result *= a,temp_result值为a^13( a^12*a )
反复平方法求幂运算的两个算法的比较:
方法一与方法二的比较:
方法二更好,因为M = M*C, 中的C是个常量,对与固定的C可以有些优化算法;
(1)根据C的二进制形式计算M*C 的方法称之为 binary method。
(2)加速binary算法可以先将C分解(factor),再利用binary method,
称之为(factor and binary method)
反复平方法求幂运算的应用:
1.模幂运算:M =C^d mod N
只需在原算法中的每步运算中,加上mod N 就可以解决;
该算法可以用于RSA算法中。
利用Montgomery算法改写算法中求mod运算可以加速上述算法的执行。
代码如下:
LL pow_mod(LL a, LL b, LL MOD){//a的b次方求余MOD
LL ret = 1;
while(b){
if(b & 1) ret = (ret * a) % MOD; //此处加mod
a = (a * a) % MOD; //此处加mod
b >>= 1;
}
return ret;
}