热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

(详细)快速幂算法及效率分析大数幂乘快速幂取余(附测试时间)

快速幂问题:求a^b%m,即a的b次方对m取余的结果。只要学过C语言的循环就可以写出最简单的朴素版本:朴素版typedeflong

快速幂

问题:求a^b % m,即a的b次方对m取余的结果。

只要学过C语言的循环就可以写出最简单的朴素版本:


朴素版

typedef long long LL;
LL normal_Edition(LL a, LL b, LL m)
{//朴素版本LL ans = 1;for(int i = 0; i < b; ++i)ans *= a;ans = ans % m;return ans;
}

时间复杂度O(b),空间复杂度达到了惊人的O(a^b)的指数级。

考虑问题规模:a <(10 ^ 9), b <(10 ^ 6),m <(10 ^ 9)。

问题出现了:这样的算法在求a的b次幂的时候就极其容易溢出,即便用long long也是如此。


我们有取模运算的运算法则:
(a * b) % p = (a % p * b % p) % p


在这里不加证明的使用。

所以在这个前提下,我们可以在求a的b次幂的同时,每次对m进行%操作,这样可以使得ans不会越界。

根据思想可以写出以下代码:


改进版

LL advanced_Edition(LL a, LL b, LL m)
{//普通优化LL ans = 1;for(int i = 0; i < b; ++i)ans = ans * a % m;return ans;
}

时间复杂度O(b),空间复杂度为的O(max(a,m)),此时溢出的问题是得到了解决。

但如果考虑问题规模:a <(10 ^ 9), b <(10 ^ 18),m <(10 ^ 9),这样显然又无法满足需要了。


快速幂

这时候引入快速幂,它基于二分的思想,所以也称为二分幂。


不难注意到这样的事实:求a^b的过程中,b只有两种情况:奇数或偶数。

若b为偶数,则a^b = a^(b/2) * a^(b/2)。

若b为奇数,则a^b = a * a ^ (b-1)。且从下一次开始,b必然为偶数的情况。


基于以上思想就可以成功把求幂过程的复杂度降为(logb)。这样就可以满足原规模的数据了。

根据思想写出以下递归版本代码:


递归版:

LL fast_Rec_Edition(LL a, LL b, LL m)
{//快速幂 递归实现LL ans = 1;if(b == 0) return 1;if(b & 1) return a * fast_Rec_Edition(a, b - 1, m) % m; // 奇数,用位运算更快else{LL mul = fast_Rec_Edition(a, b / 2, m);return mul * mul % m;//这一步一定不能写成//return fast_Rec_Edition(a, b / 2, m) * fast_Rec_Edition(a, b / 2, m);//这样会每次多次调用,使得复杂度回归O(b)}return ans;
}

我们也可以进一步写出迭代版本的代码(From算法笔记):

在这里插入图片描述


迭代版:

LL fast_Iter_Edition(LL a, LL b, LL m)
{//快速幂迭代实现LL ans = 1;while(b){if(b & 1) // 最低位是1ans = ans * a % m;a = a * a % m; // a², %m防止溢出b >>= 1; // 右移}return ans;
}

性能测试

#include
using namespace std;typedef long long LL;//求 a^b % m 的几种写法及复杂度分析
LL normal_Edition(LL a, LL b, LL m)
{//朴素版本LL ans = 1;for(int i = 0; i < b; ++i)ans *= a;ans = ans % m;return ans;
}LL advanced_Edition(LL a, LL b, LL m)
{//普通优化LL ans = 1;for(int i = 0; i < b; ++i)ans = ans * a % m;return ans;
}LL fast_Rec_Edition(LL a, LL b, LL m)
{//快速幂 递归实现LL ans = 1;if(b == 0) return 1;if(b & 1) return a * fast_Rec_Edition(a, b - 1, m) % m;else{LL mul = fast_Rec_Edition(a, b / 2, m);return mul * mul % m;}return ans;
}LL fast_Iter_Edition(LL a, LL b, LL m)
{//快速幂迭代实现LL ans = 1;while(b){if(b & 1)ans = ans * a % m;a = a * a % m;b >>= 1;}return ans;
}int main()
{time_t start, over;LL a, b, m = 7, ans;scanf("%lld %lld %lld", &a, &b, &m);//-----------------------------------------------------------------start = clock();ans = normal_Edition(a, b, m);over = clock();printf("使用朴素版, %lld^%lld %% %lld = %lld所用时间为 %f s\n", a, b, m, ans, (double)(over - start)/CLOCKS_PER_SEC);//-----------------------------------------------------------------start = clock();ans = advanced_Edition(a, b, m);over = clock();printf("使用优化版, %lld^%lld %% %lld = %lld所用时间为 %f s\n", a, b, m, ans, (double)(over - start)/CLOCKS_PER_SEC);//-----------------------------------------------------------------start = clock();ans = fast_Rec_Edition(a, b, m);over = clock();printf("使用快速幂递归版, %lld^%lld %% %lld = %lld所用时间为 %f s\n", a, b, m, ans, (double)(over - start)/CLOCKS_PER_SEC);//-----------------------------------------------------------------
//-----------------------------------------------------------------start = clock();ans = fast_Iter_Edition(a, b, m);over = clock();printf("使用快速幂迭代版, %lld^%lld %% %lld = %lld所用时间为 %f s\n", a, b, m, ans, (double)(over - start)/CLOCKS_PER_SEC);
//-----------------------------------------------------------------return 0;
}

两组测试

1:10000 ^ 500000000 mod 71

朴素版已由于溢出导致结果错误,而优化版虽然能得出正确答案,但耗时相当长,快速幂的两种实现几乎没有任何耗时。

在这里插入图片描述

2:1003 ^ 9223372036854775807 mod 71

在这里插入图片描述


总结:

快速幂/二分幂的思想还是非常有用的,同时作为简单的算法也值得了解。

递归/迭代的写法在效率上差距不明显,可以采用自己习惯的写法。


推荐阅读
  • 本文通过一个简单的 C++ 示例,深入分析了当使用 `vector::resize` 方法调整向量大小时,对象的构造函数和析构函数被调用的具体情况。示例代码展示了如何创建一个包含自定义类的对象的向量,并通过调整其大小来观察构造和析构的过程。 ... [详细]
  • 本文介绍如何在Ubuntu环境下为OpenWrt系统构建并安装首个'Hello World'应用程序的IPK包。文章不仅涵盖了基本的环境搭建,还详细说明了代码编写、Makefile配置及最终的IPK包生成与安装过程。 ... [详细]
  • 本文探讨了C++编程语言中声明与定义的区别,以及如何通过内部连接和外部连接来组织源文件,确保代码的正确链接与编译。文章详细解析了不同类型、变量、函数以及类的连接属性,并提供了实用的示例。 ... [详细]
  • 辗转相减法在求解最大等比值问题中的应用
    本文探讨了如何利用辗转相减法解决X星球大奖赛中奖金分配的数学问题,通过分析给定的数据点,计算出可能的最大等比值。 ... [详细]
  • 本文详细介绍了Manacher算法,该算法能够在O(n)时间内找到字符串中的最长回文子串。通过对字符串进行预处理,并使用动态规划的思想,Manacher算法能够高效地解决这一问题。 ... [详细]
  • 本文介绍了如何计算给定数组中所有非质数元素的总和,并提供了多种编程语言的实现示例。 ... [详细]
  • 本文探讨了随着并发需求的增长,MySQL数据库架构如何从简单的单一实例发展到复杂的分布式系统,以及每一步演进背后的原理和技术解决方案。 ... [详细]
  • 精通C++并非易事,为何它比其他语言更难掌握?这主要归因于C++的设计理念,即不强迫用户接受特定的编程风格或限制创新思维。本文探讨了如何有效学习C++,并介绍了几本权威的学习资源。 ... [详细]
  • 本文介绍如何在指定的Module中通过配置build.gradle文件来生成自定义名称和路径的JAR文件,适用于Gradle 2.4及以上版本的Android Studio环境。 ... [详细]
  • addcslashes—以C语言风格使用反斜线转义字符串中的字符addslashes—使用反斜线引用字符串bin2hex—函数把包含数据的二进制字符串转换为十六进制值chop—rt ... [详细]
  • 近期探讨了‘内部螺旋矩阵算法’的实现细节,并深入分析了面向对象编程中的可扩展性问题。基于这些讨论,本文通过引入桥梁设计模式对原有代码进行了优化与重构,以增强代码的灵活性和可维护性。 ... [详细]
  • 数据结构双链表和双循环链表(C语言代码)
    双链表和双循环链表单链表双循环链表单链表单向链表特点:  1.可以轻松的到达下一个节点,但是回到前一个节点是很难的.  2.只能从头遍历到尾或者从尾遍历到头(一般从头 ... [详细]
  • Microsoft即将发布WPF/E的CTP(Community Technology Preview)和SDK,标志着RIA(Rich Internet Application)技术的新里程碑。更多详情及下载链接请参见MSDN官方页面。 ... [详细]
  • Imreadingthisdocument:http:software.intel.comen-usarticlesinteractive-ray-tracing我正在阅读这个文 ... [详细]
  • MainActivityimportandroid.app.Activity;importandroid.os.Bundle;importandroid.os.Handler;im ... [详细]
author-avatar
手机用户2502920645
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有