快速幂——步步演化

快速幂运算,提高算法效率的绝佳选择
我们首先来了解一下“取模”运算的运算法则:(具体的证明感兴趣的同学可以问度娘)

(a + b) % p = (a % p + b % p) % p (1)

(a - b) % p = (a % p - b % p ) % p (2)

(a * b) % p = (a % p * b % p) % p (3

其中我们只要关注第“3”条法则即可:(a * b) % p = (a % p * b % p) % p ,我们仔细研究一下这个运算法则,会发现多个因子连续的乘积取模的结果等于每个因子取模后的乘积再取模的结果。也就是说,我们如果要求:

(abc)%d=(a%db%dc%d)%d;

因此,我们可以借助这个法则,只需要在循环乘积的每一步都提前进行“取模”运算,而不是等到最后直接对结果“取模”,也能达到同样的效果。
Java代码如下

package com.lzp.util;

/**
 * @Author LZP
 * @Date 2021/2/21 8:21
 * @Version 1.0
 *
 * 快速幂
 */
public class QuickPow {

    /**
     * 求底数为a,指数为n的幂
     * 效率极低,而且结果不能表示出来
     * @param a 底数
     * @param n 指数
     */
    @Deprecated
    public static long pow(long a, long n) {
        if (a == 0) {
            System.out.println("幂运算的底数不能为0");
        }
        if (a == 1) {
            return 1;
        }
        if (n == 0) {
            return 1;
        }
        if (n == 1) {
            return a;
        }

        long result = 1;
        for (int i = 0; i < n; i++) {
            result *= a;
        }

        return result;
    }

    /**
     * 求底数为a,指数为n的幂
     * 加上一个求模的数,是为了防止指数的爆炸式增长会超出我们目前计算机中数的表示范围
     * 效率还是较低,但结果能表示出来
     * @param a 底数
     * @param n 指数
     * @param mod 模
     */
    public static long pow(long a, long n, long mod) {
        if (a == 0) {
            System.out.println("幂运算的底数不能为0");
        }
        if (a == 1) {
            return 1;
        }
        if (n == 0) {
            return 1;
        }
        if (n == 1) {
            return a;
        }

        long result = 1;
        for (int i = 0; i < n; i++) {
            result *= a;
            result %= mod;
        }

        return result % mod;
    }

    /**
     * 快速幂
     * 求底数为a,指数为n的幂
     * 加上一个求模的数,是为了防止指数的爆炸式增长会超出我们目前计算机中数的表示范围
     * 效率明显提高,且结果能表示出来
     * @param a 底数
     * @param n 指数
     * @param mod 模
     */
    public static long qPow(long a, long n, long mod) {
        if (a == 0) {
            System.out.println("幂运算的底数不能为0");
        }
        if (a == 1) {
            return 1;
        }
        if (n == 0) {
            return 1;
        }
        if (n == 1) {
            return a;
        }

        long result = 1;
        while (n > 0) {
            if (n % 2 == 0) {
                // 缩指数操作后:数的结果跟原来数的结果一模一样
                // 指数为偶数
                // 可以缩指数,即对半缩,指数缩小一半,底数扩大一倍(即为原来底数的两倍)
                n = n >> 1;
                a *= a;
                a %= mod;
            } else {
                // 指数为奇数
                n = n - 1;
                // 把当前底数累乘到result临时变量中,因为每次底数都在变化
                result *= a;
                result %= mod;
                // 如果此时n已经为0,后面的a就再也不会被累乘到result中了,即可以跳出循环
                n = n >> 1;
                a *= a;
                a %= mod;
            }
        }

        return result % mod;
    }

    /**
     * 优化快速幂
     * 求底数为a,指数为n的幂
     * 加上一个求模的数,是为了防止指数的爆炸式增长会超出我们目前计算机中数的表示范围
     * 效率明显提高,且结果能表示出来
     * @param a 底数
     * @param n 指数
     * @param mod 模
     */
    public static long qPowPlus(long a, long n, long mod) {
        if (a == 0) {
            System.out.println("幂运算的底数不能为0");
        }
        if (a == 1) {
            return 1;
        }
        if (n == 0) {
            return 1;
        }
        if (n == 1) {
            return a;
        }

        long result = 1;
        while (n > 0) {
            if (n % 2 == 1) {
                // 指数为奇数
                n = n - 1;
                // 把当前底数累乘到result临时变量中,因为每次底数都在变化
                result *= a;
                result %= mod;
            }
            // 缩指数操作后:数的结果跟原来数的结果一模一样
            // 指数为偶数
            // 可以缩指数,即对半缩,指数缩小一半,底数扩大一倍(即为原来底数的两倍)
            // 如果此时n已经为0,后面的a就再也不会被累乘到result中了,即可以跳出循环
            n = n >> 1;
            a *= a;
            // 注意:底数也要求模
            a %= mod;
        }

        return result % mod;
    }
}

测试数据

package com.lzp.test.day12;

import com.lzp.util.QuickPow;

/**
 * @Author LZP
 * @Date 2021/2/21 9:05
 * @Version 1.0
 */
public class QuickPowTestTime {
    public static void main(String[] args) {

        System.out.println("============测试时间============");
        System.out.println("===========没有用快速幂============");
        long start = System.currentTimeMillis();
        System.out.println(QuickPow.pow(2, 1000000000, 1000));
        long end = System.currentTimeMillis();
        System.out.println(end - start + "ms");

        System.out.println("=============用快速幂==============");
        long start2 = System.currentTimeMillis();
        System.out.println(QuickPow.qPow(2, 1000000000, 1000));
        long end2 = System.currentTimeMillis();
        System.out.println(end2 - start2 + "ms");

        System.out.println("=============用快速幂优化版==============");
        long start3 = System.currentTimeMillis();
        System.out.println(QuickPow.qPowPlus(2, 1000000000, 1000));
        long end3 = System.currentTimeMillis();
        System.out.println(end3 - start3 + "ms");

    }
}

运行结果

posted @   没有你哪有我  阅读(72)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
点击右上角即可分享
微信分享提示