本文的非递归部分转载自:http://www.cnblogs.com/wallace/archive/2009/12/27/1633683.html
先上算法
1.递归算法
//幂运算的递归算法
long pow(long x, int n){
if(n == 0) return 1;
if(n == 1) return x;
if(n % 2 == 0){
return pow(x*x, n/2);
}else{
return pow(x*x, n/2)*x;
}
}
注意:在算法分析中还说明了:
用:
return pow(pow(x, 2), n/2);
return pow(pow(x, n/2), 2);
return pow(x, n/2)*pow(x, n/2)
都是不行的,具体原因参考算法分析,主要就是明白,我们的目的是降次和分解
2.非递归算法
//幂运算的非递归运算,由低次幂逐渐升级即:x, x^2, x^4, x^8, ...
long pow2(long x, int n){
long pw = 1;
while (n > 0) {
//奇数二进制最后一位必定是1。如果是奇数,则乘以x(下面x在不断增大,到最后必定n=1,即奇数)
if (n & 1) // n & 1 等价于 (n % 2) == 1
pw *= x;
x *= x;
n >>= 1; // n >>= 1 等价于 n /= 2
}
return pw;
}
里面都是用了位运算,这个能提高效率,需要掌握!具体的过程分析见下面
快速求正整数次幂,当然不能直接死乘。举个例子:
3 ^ 999 = 3 * 3 * 3 * … * 3
直接乘要做998次乘法。但事实上可以这样做,先求出2^k次幂:
3 ^ 2 = 3 * 3
3 ^ 4 = (3 ^ 2) * (3 ^ 2)
3 ^ 8 = (3 ^ 4) * (3 ^ 4)
3 ^ 16 = (3 ^ 8) * (3 ^ 8)
3 ^ 32 = (3 ^ 16) * (3 ^ 16)
3 ^ 64 = (3 ^ 32) * (3 ^ 32)
3 ^ 128 = (3 ^ 64) * (3 ^ 64)
3 ^ 256 = (3 ^ 128) * (3 ^ 128)
3 ^ 512 = (3 ^ 256) * (3 ^ 256)
再相乘:
3 ^ 999
= 3 ^ (512 + 256 + 128 + 64 + 32 + 4 + 2 + 1)
= (3 ^ 512) * (3 ^ 256) * (3 ^ 128) * (3 ^ 64) * (3 ^ 32) * (3 ^ 4) * (3 ^ 2) * 3
这样只要做16次乘法。即使加上一些辅助的存储和运算,也比直接乘高效得多(尤其如果这里底数是成百上千位的大数字的话)。
我们发现,把999转为2进制数:1111100111,其各位就是要乘的数。这提示我们利用求二进制位的算法(其中mod是模运算):
REVERSE_BINARY(n)
1 while (n > 0)
2 do output (n mod 2)
3 n ← n / 2
这个算法给出正整数n的反向二制进位,如6就给出011(6的二进制表示为110)。事实上这个算法对任意的p进制数是通用的,只要把其中的2换成p就可以了。
如何把它改编为求幂运算?我们发现这个算法是从 低位向高位做的,而恰好我们求幂也想从低次幂向高次幂计算(参看前面的例子)。而且我们知道前面求出的每个2^k次幂只参与一次乘法运算,这就提示我们并 不把所有的中间结果保存下来,而是在计算出它们后就立即运算。于是,我们要做的就是把输出语句改为要做的乘法运算,并在n减少的同时不断地累积求2^k次 幂。
还是看算法吧:
POWER_INTEGER(x, n)
1 pow ← 1
2 while (n > 0)
3 do if (n mod 2 = 1)
4 then pow ← pow * x
5 x ← x * x
6 n ← n / 2
7 return pow
不难看出这个算法与前面算法的关系。在第1步给出结果的初值1,在while循环内进行运算。3、4中的if语句就来自REVERSE_BINARY的输出语句,不过改成了如果是1则向pow中乘。5句则是不断地计算x的2^k次幂,如对前面的例子就是计算2^2、2^4、2^8、…、2^512。
应该指出,POWER_INTEGER比 前面分析的要再多做两次乘法,一次是向pow中第一次乘x,如2^1也要进行这个乘法;另一次则是在算法的最后,n除以2后该跳出循环,而前面一次x的自 乘就浪费掉了(也可以考虑改变循环模式优化掉它)。另外,每趟while循环都要进行一次除法和一次模运算,这多数情况下除法和模运算都比乘法慢许多,不 过好在我们往往可以用位运算来代替它。
相应的C++代码如下
NumberType pow_n(NumberType x, unsigned int n)
{
NumberType pw = 1;
while (n > 0) {
if ((pw % 2) == 1)
pw *= x;
x *= x;
n /= 2;
}
return pw;
}
进行简单的优化后则有:
NumberType optimized_pow_n(NumberType x, unsigned int n)
{
NumberType pw = 1;
while (n > 0) {
if (n & 1) // n & 1 等价于 (n % 2) == 1
pw *= x;
x *= x;
n >>= 1; // n >>= 1 等价于 n /= 2
}
return pw;
}
注1:快速求幂算法POWER_INTEGER常被写成递归的形式,算法实质完全相同,但却是无必要的。
注2:这个算法并不是做乘法数最少的,但多数情况下是足够快并且足够简单的。如果单纯追求做乘法数最少,则未必应该用2^k次幂进行计算。如果还允许做除法,则问题会进一步复杂化。
如:
x ^ 2 = x * x
x ^ 4 = (x ^ 2) * (x ^ 2)
x ^ 8 = (x ^ 4) * (x ^ 4)
x ^ 16 = (x ^ 8) * (x ^ 8)
x ^ 31 = (x ^ 16) * (x ^ 8) * (x ^ 4) * (x ^ 2) * x
要8次乘法。
x ^ 2 = x * x
x ^ 4 = (x ^ 2) * (x ^ 2)
x ^ 8 = (x ^ 4) * (x ^ 4)
x ^ 10 = (x ^ 8) * (x ^ 2)
x ^ 20 = (x ^ 10) * (x ^ 10)
x ^ 30 = (x ^ 20) * (x ^ 10)
x ^ 31 = (x ^ 30) * x
只要7次乘法。
x ^ 2 = x * x
x ^ 4 = (x ^ 2) * (x ^ 2)
x ^ 8 = (x ^ 4) * (x ^ 4)
x ^ 16 = (x ^ 8) * (x ^ 8)
x ^ 32 = (x ^ 16) * (x ^ 16)
x ^ 31 = (x ^ 32) / x
只要6次乘或除法。
不过具体得出上述乘(除)法数更少的算法会变得相当复杂,在许多情况下时间收益还会得不偿失。因此往往并不实用。ACM Japan 2006中有一道题即要求计算最少乘法数,可参看:
http://acm.pku.edu.cn/JudgeOnline/problem?id=3134
zz from:http://www.cnblogs.com/wallace/archive/2009/12/27/1633683.html
分享到:
相关推荐
在C语言中实现非递归的快速幂,主要是通过迭代的方式来完成。下面我们将详细探讨这个算法及其C语言实现。 快速幂算法基于以下基本思想:计算`a^n`可以通过分解`n`为二进制表示,然后反复平方和乘法来实现。例如,...
创建一个函数 power来为任意数字做幂运算n ** i #递归 def power(n,i): if i==1: return n return n*power(n,i-1) print(power(2,4)) 练习2 创建一一个函数,用来检查一个任意的字符串是否是回文字符串 ,如果...
1. **基本情况**:如果 \(n = 0\),则返回 \(1\)(任何非零数字的 \(0\) 次幂都是 \(1\))。 2. **递归情况**:如果 \(n > 0\),则返回 \(x \times x^{n-1}\)。这里的关键是递归地计算 \(x^{n-1}\)。 #### 递归求...
在JS中,可以通过递归和非递归的方式来实现求幂运算。 非递归求幂算法通常采用的是二进制的指数分解法,这种方法的核心思想是将指数n表示为2的幂次和的形式。具体实现时,通过不断地将n除以2并取余数来得到底数相乘...
在Java中,斐波那契数列可以用递归和非递归两种方法来实现。 1. **递归实现**: 递归实现是最直观的方式,它通过函数自身调用来解决问题。在上述代码中,`feibonaci1` 方法就使用了递归。当计算 `feibonaci1(n)` ...
- **调试**:递归函数往往比非递归函数更难理解和调试,因为它们涉及到调用栈的管理。 在使用递归时,要特别注意设计正确的递归规则,避免陷入无限循环,并确保所有可能的输入都能正确地导向基本情况。同时,通过...
非递归实现通常涉及动态规划或循环结构,如Fibonacci序列的矩阵快速幂算法。 1. **组合问题**:不考虑元素顺序,只需计算n个元素中取k个的组合数。 2. **递归公式**:C(n,k) = C(n-1,k-1) + C(n-1,k),对于k=0和k=n...
这篇文章讲述了CIC基本原理,并将CIC转换为非递归结构形式,对于非2的幂次倍抽取率,比如,24、40、180等非2次幂倍抽取,将抽取倍数分解为2P3K4M5T7R等等形式,每级为2/3/4/5/7/11/...等等倍抽取,这样将CIC设计难度...
通过部分分式展开和幂级数展开可求得 \(a_n\) 的闭式表达式,进而得出 Hanoi 塔问题的时间复杂度为 \(O(2^n)\)。 综上所述,递归方程求解方法包括递推法、公式解法以及生成函数法等几种常用方法,每种方法都有其...
- **非递归函数实现**: ```c++ double fact(int n) { int i = 1; double f = 1; while (i ) { f *= i; i++; } return f; } ``` 2. **幂运算** (`power`) - **递归公式**:`base^exp = base * base^...
在Python编程中,递归是一种强大的工具,常用于解决复杂问题。本文主要讲解如何使用递归方法实现求集合的幂集。...在处理大规模数据时,可以考虑使用非递归的迭代方式或动态规划等其他算法来优化性能。
用java写的一些常用算法 如快速排序、归并排序、非递归的归并排序、堆排序等 后续还会加上其他常用算法 用java写的一些常用算法 如快速排序、归并排序、非递归的归并排序、堆排序等 后续还会加上其他常用算法 用java...
本文将详细介绍快速幂算法的基本原理、递归实现与非递归实现,并通过实例来展示其应用。 #### 二、基本概念 **快速幂**是一种用来高效计算形如 \(a^n\) 的算法,其中 \(a\) 和 \(n\) 分别是基数和指数。该算法的...
Ackermann函数,这是一个在计算机科学领域中著名的非递归可计算函数,由荷兰数学家皮埃尔·阿克曼在1928年提出。它主要用作展示递归理论中的某些概念,例如递归函数的存在性和复杂度。该函数不是线性的、阶乘的或是...
- 使用**快速求幂取模**:如果需要计算\(2^{11} \mod 7\),可以使用上面给出的非递归实现。 #### 五、总结 通过上述介绍,我们可以看出快速幂算法是一种非常实用且高效的算法。它不仅在理论上有很好的时间复杂度...
##### 非递归实现 ```c int pow(int a, int n) { int result = 1, flag = a; while (n != 0) { if (n & 1) result *= flag; flag *= flag; n >>= 1; } return result; } ``` #### 五、快速幂取模 在实际...
对于非递归算法来说,时间复杂度的分析相对直观,而对于递归算法,由于其自身的调用特性,分析起来更为复杂。递归算法是一种常见的程序设计方法,它通过函数调用自身来解决问题。 递归算法在执行过程中会产生大量的...
易语言的核心理念是“易学易用”,它的语法简洁明了,大部分指令和函数都是以中文命名,使得非英语背景的程序员也能轻松掌握。它支持多种编程范式,包括过程式、面向对象以及组件化编程,具有丰富的内置函数和类库,...
在易语言中,递归算法和进制转换是两个重要的概念,它们在程序设计中有着广泛的应用。 递归算法是一种解决问题的方法,它通过调用自身来解决问题或简化问题。在易语言中,实现递归通常涉及到函数或过程的自调用。...
源码可能还会包含错误处理部分,如检查输入是否合法(x和y是否为非负数,y是否为整数等),以及优化策略,如使用缓存存储最近计算过的幂次结果,避免重复计算。 此外,为了使程序更易于理解和使用,源码可能还包含...