别再说你懂快速幂算法了,看完这篇文章你才会懂

2024-08-31 09:12
文章标签 算法 快速 篇文章

本文主要是介绍别再说你懂快速幂算法了,看完这篇文章你才会懂,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

快速幂算法:你以为很简单,但其实暗藏玄机!

你以为快速幂算法只是一个普通的数学公式?天真!你以为这个算法只能用在简单的幂运算上?大错特错!很多人都觉得快速幂算法只是计算大整数幂的一个小工具而已,但真的是这样吗?今天,我——干货哥,就要用一篇爆款博文来颠覆你对快速幂算法的认知。准备好了吗?接下来我将揭开快速幂的神秘面纱,让你看到它背后的深层奥义!

快速幂算法到底是什么?

先来看看这段话,你以为在计算 (x^n) 的时候,笨办法就是把 (x) 乘以自己 (n) 次?那么时间复杂度就是 (O(n)),这效率得多低啊!但是,聪明的程序员早就不这样干了。快速幂的出现,把这个复杂度直接干到 (O(\log n)),就是这么猛!怎么做到的呢?通过一个小小的数学变换:把幂次的计算拆分成二分法的思路。

核心奥义

快速幂的精髓就在于指数拆半!举个例子,计算 (x^{13}),聪明人不会老老实实地算 (x \times x \times \ldots ) ,而是会想到:
[
x^{13} = x \times (x2)6
]
看出来了吗?指数直接被减半!再来一个,(x^{10} = (x5)2),一步到位,轻松搞定。是不是感觉打开了新世界的大门?

快速幂的两种终极实现方式

很多人觉得:“唉呀,这个快速幂是不是实现起来特别复杂?” 不!事实完全相反!干货哥今天就教你两种写法,简简单单几行代码,直接搞定!

1. 递归实现:看似简单,实则霸气!

递归实现快速幂,那真是优雅得一塌糊涂。每一次递归调用都在减半指数,代码简洁到让你一秒就能看懂,但又内含无穷玄妙。

#include <stdio.h>// 递归实现快速幂
long long power(long long x, unsigned int n) {if (n == 0) return 1; // 任何数的 0 次幂为 1long long half = power(x, n / 2); // 计算 x^(n/2)if (n % 2 == 0)return half * half; // 如果 n 为偶数elsereturn x * half * half; // 如果 n 为奇数
}int main() {long long x = 2;unsigned int n = 10;printf("%lld^%u = %lld\n", x, n, power(x, n));return 0;
}

是不是超级简洁?但你可别小看它!这就是算法的美感所在,一行代码,就是一层哲学!

2. 迭代实现:更高效的选择!直接拿捏CPU!

但是!你以为递归就完美无缺了吗?当然不是!有时候递归会带来函数调用的开销,怎么办?用迭代!它避免了递归的栈开销,性能上压递归一头,绝对是不二选择!

#include <stdio.h>// 迭代实现快速幂
long long power(long long x, unsigned int n) {long long result = 1;while (n > 0) {if (n % 2 == 1) { // 如果 n 是奇数result *= x;}x *= x; // 平方底数n /= 2; // 指数减半}return result;
}int main() {long long x = 2;unsigned int n = 10;printf("%lld^%u = %lld\n", x, n, power(x, n));return 0;
}

你以为快速幂就是这样了?别急!还有大招!

3. 快速幂的模运算:大数计算的杀手锏!

很多人都遇到过这样的问题:幂的计算结果太大,分分钟爆掉变量!怎么办?直接对结果取模!快速幂的模运算实现,才是干掉大数溢出的最佳方式!不要怀疑,这个方法简直就是程序员的福音。

#include <stdio.h>// 快速幂的模运算实现
long long modPower(long long x, unsigned int n, int mod) {long long result = 1;x = x % mod; // 先对 x 取模,避免后续溢出while (n > 0) {if (n % 2 == 1) { // 如果 n 是奇数result = (result * x) % mod;}x = (x * x) % mod; // 平方底数并取模n /= 2; // 指数减半}return result;
}int main() {long long x = 2;unsigned int n = 10;int mod = 1000000007;printf("%lld^%u mod %d = %lld\n", x, n, mod, modPower(x, n, mod));return 0;
}

每一步都稳如泰山,计算结果再大也不会翻车。这样的算法,还不赶紧学会?

干货哥总结:快速幂并非你想象的那么简单!

好了,现在你已经知道快速幂算法的多种实现方式,递归、迭代、模运算,一个都不能少!你以为算法就是简单的实现?错!真正的算法是如何在实际场景中做到最优。快速幂看似基础,实则可以优化的地方多如牛毛。比如:

  • 位运算优化:谁说只有加法和乘法能加速?位运算轻松搞定!
  • 分支预测优化:减少CPU的分支错猜,简直快到起飞!
  • 汇编级优化:在极致性能的场景下,直接上内联汇编,飙到天上去!

汇编级优化通常是针对特定的硬件平台和编译器来进行的。它能够最大化地利用底层指令集的特点,挖掘程序性能的极限。对于快速幂算法,利用汇编优化可以让计算效率再上一层楼。

以下是一个针对x86架构的示例,使用GNU汇编(AT&T语法)来实现快速幂的代码。此代码在Linux平台上使用GCC编译器。

汇编级优化:快速幂实现

#include <stdio.h>// 汇编实现快速幂
long long power_asm(long long x, unsigned int n) {long long result = 1;__asm__ ("1: \n"                   // 标签1,循环开始"testl  %1, %1\n"         // 测试n的最低位是否为0"jz     2f\n"             // 如果n为0,跳到标签2"imull  %2, %0\n"         // result *= x"2: \n"                   // 标签2,处理平方底数"mull   %2\n"             // x *= x"shrl   $1, %1\n"         // n >>= 1 (右移一位,相当于n除以2)"jnz    1b\n"             // 如果n不为0,跳回标签1继续循环: "+r" (result), "+r" (n) // 输出部分:result和n,+表示读写: "r" (x)                 // 输入部分:x: "cc"                    // 告诉编译器标志寄存器被更改);return result;
}int main() {long long x = 2;unsigned int n = 10;printf("%lld^%u = %lld\n", x, n, power_asm(x, n));return 0;
}

代码解释

  1. 寄存器使用

    • resultxn 都使用通用寄存器进行存储和操作。
    • imull 指令用于有符号整数乘法,将两个数相乘并将结果存入第一个操作数。
  2. 关键指令

    • testl %1, %1: 测试寄存器 n 的最低位是否为 0。
    • jz 2f: 如果 n 为 0,跳到标签 2(快速退出循环)。
    • imull %2, %0: 进行乘法运算,将 x 乘到 result 上。
    • shrl $1, %1: 将 n 右移一位(相当于将 n 除以 2)。
  3. 循环控制

    • 循环通过条件跳转 jnz 1b 来控制,直到 n 被移位到 0。

优化效果

  • 寄存器操作:所有计算都在寄存器内完成,避免了不必要的内存读写,极大提升了效率。
  • 指令优化:采用乘法、移位指令而非标准的函数调用,减少了CPU指令流水线的停顿。

这些才是算法的真正境界:不仅仅要能用,还要能快、能稳、能省!

你现在还敢小看快速幂吗?赶紧学起来,下次你就是别人眼中的技术大牛!

这篇关于别再说你懂快速幂算法了,看完这篇文章你才会懂的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1123476

相关文章

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

shell脚本快速检查192.168.1网段ip是否在用的方法

《shell脚本快速检查192.168.1网段ip是否在用的方法》该Shell脚本通过并发ping命令检查192.168.1网段中哪些IP地址正在使用,脚本定义了网络段、超时时间和并行扫描数量,并使用... 目录脚本:检查 192.168.1 网段 IP 是否在用脚本说明使用方法示例输出优化建议总结检查 1

Rust中的Option枚举快速入门教程

《Rust中的Option枚举快速入门教程》Rust中的Option枚举用于表示可能不存在的值,提供了多种方法来处理这些值,避免了空指针异常,文章介绍了Option的定义、常见方法、使用场景以及注意事... 目录引言Option介绍Option的常见方法Option使用场景场景一:函数返回可能不存在的值场景

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO