代码难免会有错误,还请见谅,同时也欢迎各位指出错误,我也会及时进行修改更正,谢谢。
在刷题过程中经常会见到类似a^b或者a^b%c的题目,往往题目范围给出的数进行n次乘法后,往往会超出范围,因此,一种更加简单,时间复杂度更出色的算法能够更快的得出结果。
首先,还是给出最简单的循环相乘,以方便比较。
1):
但如果对于时间没有要求,仅仅需要将正确结果输出的话,我们可以通过创建一个数组来进行对结果的存储,类似于大数阶乘中存储结果的方法,代码如下:
容易理解,a^b即通过b此循环,使b个a相乘,得到结果,这种方法只有在a ,b的值不是太大时适用,如果b的值达到10的7 次方,8次方甚至更大时,时间复杂度为O(n)的算法显然不能满足要求。
而快速幂的时间复杂度只有O(log n),显然比上面的方法快了非常多,我们从 2^11次方来理解这个算法,在开始之前,我们要熟悉一个位移运算符中的右移运算符 >>和位运算符 &。
显然, 这两个运算符都是作用于位的 。
举个例子 ,13的二进制是1101。
右移运算符 >>
int 型,32位操作系统下,占32个bits,则表示为0000 0000 0000 0000 0000 0000 0000 1101, 如果是11>>1,即向右移动一位,相当于除以2,(2进制可以由十进制不断除以2得到的余数倒序写出)
>>不改变数的原值,如
int c=11;
int d=c>>1;
cout<<d;
cout<<c/2;
得到的结果是 5 5
>>= 改变数的值
如int c=11;
int d=c>>=1;
cout<<d;
cout<<c/2;
得到的结果是 5 2
1101的第四个1 丢弃 ,变为110,首位空出位置 补0(13是正数),表示为 0000 0000 0000 0000 0000 0000 0000 0110,即5,在非负数范围,a>>1于a/2相同。
位运算符&
按位与运算符"&" 的功能是当二进位均为1时,输出才为1 ,否则为0。
了解了这些,接下来我们就开始对快速幂的解释。
先上代码:
假设我们要计算2 的13次方(当然这个数不算大,这个例子只是为了解释方便)
13可以表示为二进制的1101,即a1(1*2^3)+a2(1*2^2)+a3(0*2^1)+a4(1*2^0);
2^13=a1*a2*a3*a4;
此时看起来只有4次乘法运算,但a1 a2 a3 a4都需要进行更多次的乘法运算,而我们又知道
a^1 * a^1 =a^2;
a^2 * a^2 =a^4;
a^4 * a^4=a^8;
1101又可以拆成1000+100+00+1(二进制表示)
也就是2^1000 *2^100 * 2^00 * 2*1;
表示成十进制--->2^8 *2^4 * 2^1 = 2^13;
我们知道 任何数的0次幂都为1(0的0次方不讨论),因此,当某一位为0时,不进行累乘.
首先 int ans =1;用来存储结果
a表示底数,b表示幂
从第一层循环开始解释:
一:
while (b>0)
a的b次方 b大于0时 进行下列循环,
问:为什么输入了b不为0,最后还会停止循环?
注意看 第14行b进行了 b>>=1的位运算,如果最后b的每一位有意义的数(剩下的都是0)都乘到了ans上,由于不断补0,剩下32位0,while循环自然终止。
二:
if(b&1)
这一步用来判断二进制数的末尾是0还是1,前面讲过,只有两位均为1时,才为真值,即最后一位不为0,乘法有意义(乘数不为1),让ans=ans*a;
三:
a*=a;
上面说过:
2^11 表示成十进制--->2^8 *2^2 * 2^1 = 2^11;
2^2=2^1*2^1;
2^4=2^2*2^2;
2^8=2^4*2^4;
因此,a的自乘可以使2^8的8次方的运算转为2^4的平方运算,可以减少进行乘法运算的次数,
注意:a*=a;无论if(b&1)是否成立都会执行
四:
b>>=1
b=1101 , ans=1;a=2^2;
b向右移动一位,即1101经过上述过程后,b=110
为了更好理解该过程,图:
注意:ans=ans*a是先于a的自乘进行的。