热门标签 | 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,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
  • 本文详细介绍了C语言中链表的两种动态创建方法——头插法和尾插法,包括具体的实现代码和运行示例。通过这些内容,读者可以更好地理解和掌握链表的基本操作。 ... [详细]
  • 本文详细探讨了C语言中指针的概念,特别是指针在变量和数组中的应用。通过实例讲解,帮助读者更好地掌握指针的使用方法。 ... [详细]
  • 本文介绍了几种不同的编程方法来计算从1到n的自然数之和,包括循环、递归、面向对象以及模板元编程等技术。每种方法都有其特点和适用场景。 ... [详细]
  • C语言基础入门:7个经典小程序助你快速掌握编程技巧
    本文精选了7个经典的C语言小程序,旨在帮助初学者快速掌握编程基础。通过这些程序的实践,你将更深入地理解C语言的核心概念和语法结构。 ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • C++实现经典排序算法
    本文详细介绍了七种经典的排序算法及其性能分析。每种算法的平均、最坏和最好情况的时间复杂度、辅助空间需求以及稳定性都被列出,帮助读者全面了解这些排序方法的特点。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 在Java中,this是一个引用当前对象的关键字。如何通过this获取并显示其所指向的对象的属性和方法?本文详细解释了this的用法及其背后的原理。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
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社区 版权所有