由于数学基础比较薄弱,最近在做到一个算法题时,才了解到“快速幂”这一概念。
题目地址:https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/
怎么样去理解“快速幂”?
假设现在我接到一个需求:算出2的64次方。
怎么做最快、最容易呢,总不可能真的算24次吧。通常我们会这么做
2^64 = 2^32 * 2^32
2^32 = 2^16 * 2^16
2^16 = 2^8 * 2^8
2^8 = 2^4 * 2^4
2^4 = 2^2 * 2^2
2^2 = 4
不断地削减指数,通过最小的运算次数,我们可以比较轻松地算出最后的结果。
其实这事放到计算机身上,也一样。计算机也不喜欢做太多的乘法运算(耗时),相比较多次计算,它更喜欢参与运算的数大点,长痛不如短痛。
例如计算3的10001次方,会按照下面的流程来计算。
如果指数是偶数,则直接拆分成另外两个指数。
如果是计数,则会多一个3出来,算总乘积时,也不能把这个3给漏了。
模运算
“指数爆炸”这一概念想必大家都听过,如果指数过大,那么运算的结果很容易超过Integer.MAX_VALUE 或者 Long.MAX_VALUE。
所以一般类似的算法题会要求 对最后的结果取模。模运算的一些定律我们还是要了解一点的,本篇文章用到:
(a * b) % c = ( a % c * b % c) % c
代码实现
定义一个接口,规定入参和返回值
public interface Pow {
/**
* 指数运算
* @param base 底数
* @param index 指数
* @param mod 取模
* @return 结果
*/
long calculate(long base, long index, int mod);
}
常规算法实现
public class Pow1 implements Pow{
@Override
public long calculate(long base, long index, int mod) {
long res = 1;
int count = 0;
while (index-- > 0) {
res = (res * base) % mod;
count++;
}
System.out.println("常规方法-运行次数 " + count);
return res % mod;
}
}
快速幂实现
res用来保存结果
如果指数为奇数,则乘以base,把这个多余的base算进结果里。
如果是偶数,则直接扩大base。
直到最后,指数也为1了,就可以把最后得到的base汇总到res里,并返回结果res。
public class Pow2 implements Pow {
@Override
public long calculate(long base, long index, int mod) {
long res = 1;
int count = 0;
while (index > 0) {
if(index % 2 == 1) {
index--;
res = res * base % mod;
}
index = index / 2;
base = base * base % mod;
count++;
}
System.out.println("快速幂-运行次数 " + count);
return res;
}
}
测试类
class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
solution.cost("常规方法", new Pow1());
solution.cost("快速幂", new Pow2());
}
public void cost(String name, Pow pow) {
long before = System.currentTimeMillis();
long res = pow.calculate(3, 895556423, 1000);
System.out.println(name + "-结果是 " + res);
long end = System.currentTimeMillis();
System.out.println(name + " 耗时 " + (end - before) / 1000.0);
}
}
运行结果
结论
上述结果可以看到,当指数为亿级时,如果真的做上亿次乘法,则将消耗12.5秒左右的时间。
但如果用快速幂不断地削减指数,则耗费的时间几乎等于0,可见优秀的算法带来的性能提升之大。