Java实现快速幂算法详解

 更新时间:2022年10月14日 08:58:51   作者:码农研究僧  
快速幂是用来解决求幂运算的高效方式。此算法偶尔会出现在笔试以及面试中,特意花时间研究了下这题,感兴趣的小伙伴快跟随小编一起学习一下

前言

此算法偶尔会出现在笔试以及面试中,特意花时间研究了下这题

题目:

求AB次方,并且保留最后几位数字(此题保留最后3位数)

例子:

21000,输出的结果保留3位数字

在笔试或者面试中看到此题,第一思路可能通过递归或者while遍历的想法,但细想一下,这么大的数字编程语言中任何一个变量或者计算机硬件机器也hold不住这么大的数字存储(越往后幂次越大,总是会溢出)

此时想到了海量数据如何存储:海量数据处理的高频面试题分析

那我就选择布隆过滤器:布隆过滤器的原理和实现详细分析(全)。(但可能会有误差)

硬件无法存储,那我就分片切片,甚至二进制移位来对应计算(但是我是21000次方,每一次的幂算,我都整这么复杂,这计算一个数字要花大半天??)

冷静思考后,我发现想复杂了,应该从数学推导公式下手,才能提高算法的优化

以下章节对应算法复杂度的优化

1. 暴力算法(fail)

算法如下:

/* 
* base 为底数
* power 为指数
*/
public long slowPower(long base,long power){
    long result = 1;
    
    // 依次通过for循环,将其对应的数字乘
    for(int i = 1 ;i <= power;i++){
        result *= base;
    }
    
    // 保留最后的3位数字,求余1000
    return result % 1000;
}

或者使用while循环

public long slowPower(long base,long power){
    long result = 1;
    
    while(power--){
        result *= base;
    }
    return result % 1000;
}

此处的代码执行的时候,就会出现数组越界

幂次运算,越到最后,爆炸式的增长:

对此求余是最好的想法(因为数值很大保留最后几位即可)

但数值本身就已经越界,而且爆炸增长也算不到最后的数值,更不用提及求余

2. 优化取模运算(accept)

从上面的理论可得知,在求到某一步的时候,数值已经越界,那可以提前求余在计算么

那就要从取模运算进行深入了解:取模运算百度百科

本身模运算与基本四则运算相似

  • (a + b) % p = (a % p + b % p) % p
  • (a - b) % p = (a % p - b % p ) % p
  • (a * b) % p = (a % p * b % p) % p
  • a ^ b % p = ((a % p)^b) % p

其他的重要的也可提前过一遍(哪天用得上)

结合律:

  • ((a+b) % p + c) % p = (a + (b+c) % p) % p
  • ((ab) % p * c)% p = (a * (bc) % p) % p

分配律:

  • (a+b) % p = ( a % p + b % p ) %p
  • ((a +b)% p * c) % p = ((a * c) % p + (b * c) % p) % p

特别是这个公式:(a * b) % p = (a % p * b % p) % p

算法如下:

/* 
* base 为底数
* power 为指数
*/
public long fastPower(long base,long power){
    long result = 1;
    
    // 依次通过for循环,将其对应的数字乘
    for(int i = 1 ;i <= power;i++){
        result *= base;
        result %= 1000;
    }
    
    // 保留最后的3位数字,求余1000
    return result % 1000;
}

3. 优化时间复杂度(accept)

上面的计算是21000,如果是210000000000000000000000000000那时间复杂度随着指数的增加而增加,而且迭代的次数也特别多,那优化时间复杂度么?

通过幂次的巧妙处理,将其幂次计算的迭代减少

具体如下:(计算210

pow(2,10)
= pow(4,5)
= pow(4,4) * pow(4,1)
= pow(16,2) * pow(4,1)
= pow(256,1) * pow(4,1)

  • 幂次为偶数,直接处理
  • 幂次为奇数,拆1 以及 偶数

知道所有的指数都变为1,将其相乘即为最终的结果

最终的算法:

/* 
* base 为底数
* power 为指数
*/
public long fasterPower(long base,long power){
    long result = 1;
    
    // 保证指数大于0 遍历
    while (power > 0) {
    
        // 偶数
        if (power % 2 == 0) {
            // 指数减半
            power /= 2;
            // 底数平方,记得 (模的运算优化)
            base = base * base % 1000;
            
        } else {
            // 指数为奇数
            // 拆分为1 和 偶数
            power -= 1;
            
            // result乘底数 求余 并且放在result(提前存储)
            result = result * base % 1000;
            
            // power偶数,再操作一次
            // 底数平方,记得 (模的运算优化)
            power /= 2;
            base = base * base % 1000;
        }
    }
    
    // 直接输出结果,已经不用求余了
    return result;
}

通过上面的算法发现冗余复杂了,提取公共部分合并

/* 
* base 为底数
* power 为指数
*/
public long fasterPower(long base,long power){
    long result = 1;
    
    // 保证指数大于0 遍历
    while (power != 0) {
    
        // 奇数特殊判断
        if (power % 2) {
            result = result * base % 1000;
        } 
       
        // 再次操作一次,底数平方,记得 (模的运算优化)
        // 此处之所以不用减1,是因为 power 会向下取整 ,5 / 2 = 2 
        power /= 2;
        base = base * base % 1000;
     
    }
    
    // 直接输出结果,已经不用求余了
    return result;
}

4. 优化 位运算(accept)

除2(右移),可以使用移位来计算

// 非递归
public long fastestPower(long base,long power){
    long result = 1;
    
    while (power != 0) {
    
        // 奇数特殊判断
        if (power & 1) {
            result = result * base % 1000;
        } 
       
        power >>= 1;
        base = base * base % 1000;
     
    }
    
    // 直接输出结果,已经不用求余了
    return result;
}

通过上面的层层递进进行优化,自然也可用递归

// 递归
public long fastestPower(long base,long power){
    if(power == 0) {
        return 1;
    }
    
    return fastestPower(base * base, power >> 1) * (power & 1 == 1 ? base : 1);
}

以上就是Java实现快速幂算法详解的详细内容,更多关于Java快速幂算法的资料请关注脚本之家其它相关文章!

相关文章

  • Java中的对象和引用详解

    Java中的对象和引用详解

    这篇文章主要介绍了Java中的对象和引用详解的相关资料,需要的朋友可以参考下
    2017-05-05
  • Spring Boot 将yyyy-MM-dd格式的文本字符串直接转换为LocalDateTime出现的问题

    Spring Boot 将yyyy-MM-dd格式的文本字符串直接转换为LocalDateTime出现的问题

    这篇文章主要介绍了Spring Boot 将yyyy-MM-dd格式的文本字符串直接转换为LocalDateTime出现的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Javas使用Redlock实现分布式锁过程解析

    Javas使用Redlock实现分布式锁过程解析

    这篇文章主要介绍了Javas使用Redlock实现分布式锁过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • SpringBoot整合Redis及Redis工具类撰写实例

    SpringBoot整合Redis及Redis工具类撰写实例

    这篇文章主要介绍了SpringBoot整合Redis及Redis工具类撰写实例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • springboot实现分页功能的完整代码

    springboot实现分页功能的完整代码

    Spring Boot是一个快速开发框架,它提供了很多便捷的功能,其中包括分页查询,下面这篇文章主要给大家介绍了关于springboot实现分页功能的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • 解决IDEA光标变成白色粗条的问题

    解决IDEA光标变成白色粗条的问题

    这篇文章主要介绍了解决IDEA光标变成白色粗条的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Java结构型设计模式中代理模式示例详解

    Java结构型设计模式中代理模式示例详解

    代理模式(Proxy Parttern)为一个对象提供一个替身,来控制这个对象的访问,即通过代理对象来访问目标对象。本文将通过示例详细讲解一下这个模式,需要的可以参考一下
    2022-09-09
  • 详解Mybatis逆向工程中使用Mysql8.0版本驱动遇到的问题

    详解Mybatis逆向工程中使用Mysql8.0版本驱动遇到的问题

    今天在使用 8.0.12 版的 mysql 驱动时遇到了各种各样的坑。这篇文章主要介绍了详解Mybatis逆向工程中使用Mysql8.0版本驱动遇到的问题,感兴趣的小伙伴们可以参考一下
    2018-10-10
  • 详解java中的阻塞队列

    详解java中的阻塞队列

    这篇文章主要介绍了java中的阻塞队列的相关知识,文中代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下
    2020-06-06
  • Java防止xss攻击附相关文件下载

    Java防止xss攻击附相关文件下载

    首先说一下思路,防止这种类似于注入攻击,就是使用拦截器(Filter)处理特殊字符或过滤特殊字符 今天介绍一个方法,利用覆盖Servlet的getParameter方法达到处理特殊字符的目的来解决(防止)Xss攻击 web.xml,需要的朋友可以参考下
    2020-02-02

最新评论