热门标签 | 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

在这里插入图片描述


总结:

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

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


推荐阅读
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
  • 本文介绍了几种不同的编程方法来计算从1到n的自然数之和,包括循环、递归、面向对象以及模板元编程等技术。每种方法都有其特点和适用场景。 ... [详细]
  • C语言基础入门:7个经典小程序助你快速掌握编程技巧
    本文精选了7个经典的C语言小程序,旨在帮助初学者快速掌握编程基础。通过这些程序的实践,你将更深入地理解C语言的核心概念和语法结构。 ... [详细]
  • 本文介绍如何利用栈数据结构在C++中判断字符串中的括号是否匹配。通过顺序栈和链栈两种方式实现,并详细解释了算法的核心思想和具体实现步骤。 ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • C++实现经典排序算法
    本文详细介绍了七种经典的排序算法及其性能分析。每种算法的平均、最坏和最好情况的时间复杂度、辅助空间需求以及稳定性都被列出,帮助读者全面了解这些排序方法的特点。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 在Java中,this是一个引用当前对象的关键字。如何通过this获取并显示其所指向的对象的属性和方法?本文详细解释了this的用法及其背后的原理。 ... [详细]
  • 本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文探讨了如何使用自增和自减运算符遍历二维数组中的元素。通过实例详细解释了指针与二维数组结合使用的正确方法,并解答了常见的错误用法。 ... [详细]
  • 本文详细介绍了C语言的起源、发展及其标准化过程,涵盖了从早期的BCPL和B语言到现代C语言的演变,并探讨了其在操作系统和跨平台编程中的重要地位。 ... [详细]
  • 本文详细介绍了Grand Central Dispatch (GCD) 的核心概念和使用方法,探讨了任务队列、同步与异步执行以及常见的死锁问题。通过具体示例和代码片段,帮助开发者更好地理解和应用GCD进行多线程开发。 ... [详细]
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社区 版权所有