目录
ID:HL_5461
引言
首先提一个问题:如何计算 ?
对于这个问题,相信很多人心里第一个想法是循环n次,每个循环里都对原数乘2。
这的确是一个简单快捷的方法,复杂度也只有O(N)。但是如果中,n等于一亿,甚至更多呢?此时我们将循环一亿次。所以,有没有一个更好的方法呢?
当然是有的,就是我们接下来将认识的快速幂算法,如果使用快速幂算法,当n为一亿时,我们只需循环27次就可以了。
一、什么是快速幂
1.的另一种计算方法
先举个例子,对 当 时。
用普通的方法,很显然,我们需要循环16次。但我们不妨换一个思路:
对于上标n,我们使用每次翻倍的方法,直到n为16。这样的话,我们只要循环4次就行了,时间复杂度也由原来的O(N)减小到O(logN)。
就这么简单,本文到此结束(bushi)。
好吧,估计很多人会有疑问:这样的话,我们难道只能计算n为2的幂的情况吗?像n为27,n为41这种非幂的情况都算不了,这个算法岂不是很鸡肋?
当然不是,但在向后介绍之前,我们先明确这样一个事实:对于 (其中n可写成,即,2
的多少幂形式)的幂函数,我们可以用如上方法快速求出。
接下来是n不是2的幂的情况:对于非2的幂的情况,我们都可将其拆为2的幂相加的情况。以,即 为例:
(为要选择的幂) |
即,也就说 ,由于1,8,32都可以写成如上表的2的幂的形式,所以可以根据上文所诉的方法很快地算出。
由此,将一个数分解为2的幂之和即为快速幂算法的关键。
2.指数n的分解
其实前面将分解为1+8+32时,一些对二进制比较熟悉的人应该已经想到了,我们还是以41为例,将其转换为二进制,即101001:
1 | 0 | 1 | 0 | 0 | 1 |
结果很明了了,要想知道如何将n分解为各个2的幂相加的形式,我们只需将其转化为二进制,就能很容易将其分解为2的幂之和。
二、代码实现
1.得到各个二进制位
很显然,首先我们需要一个循环,循环条件我们后面讨论。回忆一下初学二进制时了解到的十进制转化二进制的方法,还是以41为例:
公式 | 商 | 余数 | 2的幂 |
20 | 1 | ||
10 | 0 | ||
5 | 0 | ||
2 | 1 | ||
1 | 0 | ||
0 | 1 |
显然的,循环结束的条件是商为0,我们将其看做 n /= 2 而每次 n % 2 得数即为该位2的幂。
先实现这一小段代码:
int r = 0;//记录余数
while (n != 0) {
r = n % 2;//取余数,即二进制位
n /= 2;
}
当然我们也可以用位运算的方法:
n % 2 相当于n做位与运算,n / 2 相当于n右移一位。还是以41为例:
公式 | 商 | 余数 | 2的幂 |
20 (101001 >> 1 = 10100) | 1 (101001 & 1 = 1) | ||
10 (10100 >> 1 = 1010) | 0 (10100 & 1 = 0) | ||
5 (1010 >> 1 = 101) | 0 (1010 & 1 = 0) | ||
2 (101 >> 1 = 10) | 1 (101 & 1 = 1) | ||
1 (10 >> 1 = 1) | 0 (10 & 1 = 0) | ||
0 (1 >> 1 = 0) | 1 (1 & 1 = 1) |
所以上面那段代码也可以变为如下形式:
int r = 0;//记录余数
while (n != 0) {
r = n & 1;//取余数,即二进制位
n >>= 1;
}
2.得到每个
巴拉了半天,别忘了我们最初的目的,我们计算的是,还是以n为41为例,。我们有了每个二进制位,如何控制如1,8,32这些指数呢?我们不妨每次循环都让 a = a * a:
公式 | 商 | 余数 | a的值 |
20 | 1 | ||
10 | 0 | ||
5 | 0 | ||
2 | 1 | ||
1 | 0 | ||
0 | 1 |
着重关注余数和a的值,是不是就很明显了?当余数不为0,余数乘此时a的值,直到循环结束将所得的所有值相乘即为。
OK,上代码:
public static int qmi(int a, int n) {//a为底数,n为指数
int ans = 1;//记录乘积
while (n != 0) {
if ((n & 1) != 0) {//当余数不为0
ans *= a;//乘上此时的a
};
n >>= 1;//n右移一位
a = a * a;//a变为此时的a^2
}
return ans;
}
总结
我们从头到尾再梳理一遍,这次换个例子,以为例:
首先定义一个记录乘积的ans,6不为0,进入循环,采用位运算或者模的方式,得到其余数0,0 == 0,不等于不成立,不做处理,然后采用位运算或者除的方式,n变为3,a变为a的二次方,为,;
再次循环,余数为1,1 != 0,进入if,ans = ,n为1,a为;
继续循环,余数为1,进入if,ans = * ,n为0,a为;
循环不成立,结束返回此时ans,即 * 。
真心希望这篇博客能对大家有所帮助,若有错误,欢迎大家批评斧正!