`
hao3100590
  • 浏览: 131447 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

求幂的递归和非递归

阅读更多

本文的非递归部分转载自: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)
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(xn)
pow ← 1
while (n > 0)
3     do if (n mod 2 = 1)
4            then pow ← pow * x
5       x ← x * x
6       n ← n / 2
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

 

分享到:
评论
1 楼 liuzhiqiangruc 2012-06-03  
恩,不错,很好啊

相关推荐

    c语言非递归快速幂demo

    在C语言中实现非递归的快速幂,主要是通过迭代的方式来完成。下面我们将详细探讨这个算法及其C语言实现。 快速幂算法基于以下基本思想:计算`a^n`可以通过分解`n`为二进制表示,然后反复平方和乘法来实现。例如,...

    Python递归与非递归算法的例子,七个练习

    创建一个函数 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递归与非递归实现斐波那契数列

    在Java中,斐波那契数列可以用递归和非递归两种方法来实现。 1. **递归实现**: 递归实现是最直观的方式,它通过函数自身调用来解决问题。在上述代码中,`feibonaci1` 方法就使用了递归。当计算 `feibonaci1(n)` ...

    C、c++ 递归代码

    - **调试**:递归函数往往比非递归函数更难理解和调试,因为它们涉及到调用栈的管理。 在使用递归时,要特别注意设计正确的递归规则,避免陷入无限循环,并确保所有可能的输入都能正确地导向基本情况。同时,通过...

    递归九讲2021 7-9.zip

    非递归实现通常涉及动态规划或循环结构,如Fibonacci序列的矩阵快速幂算法。 1. **组合问题**:不考虑元素顺序,只需计算n个元素中取k个的组合数。 2. **递归公式**:C(n,k) = C(n-1,k-1) + C(n-1,k),对于k=0和k=n...

    Non-recursive CIC Filters with Integer Multiple Factors.pdf.pdf

    这篇文章讲述了CIC基本原理,并将CIC转换为非递归结构形式,对于非2的幂次倍抽取率,比如,24、40、180等非2次幂倍抽取,将抽取倍数分解为2P3K4M5T7R等等形式,每级为2/3/4/5/7/11/...等等倍抽取,这样将CIC设计难度...

    递归方程求解

    通过部分分式展开和幂级数展开可求得 \(a_n\) 的闭式表达式,进而得出 Hanoi 塔问题的时间复杂度为 \(O(2^n)\)。 综上所述,递归方程求解方法包括递推法、公式解法以及生成函数法等几种常用方法,每种方法都有其...

    1000的阶乘所有的零和尾部0的个数(用递归和不用递归两种方式实现)

    我们可以用两种方法来实现这个计算:递归和非递归(迭代)。 **递归实现**: 递归方法通常基于阶乘的定义,即n! = n × (n-1)!。在递归函数中,我们首先检查基本情况(n == 0 或 n == 1),这些情况的阶乘都是1,...

    python利用递归方法实现求集合的幂集

    在Python编程中,递归是一种强大的工具,常用于解决复杂问题。本文主要讲解如何使用递归方法实现求集合的幂集。...在处理大规模数据时,可以考虑使用非递归的迭代方式或动态规划等其他算法来优化性能。

    非常好的快速幂基础项目资源,分享出来.zip

    用java写的一些常用算法 如快速排序、归并排序、非递归的归并排序、堆排序等 后续还会加上其他常用算法 用java写的一些常用算法 如快速排序、归并排序、非递归的归并排序、堆排序等 后续还会加上其他常用算法 用java...

    算法学习笔记-快速幂.docx

    本文将详细介绍快速幂算法的基本原理、递归实现与非递归实现,并通过实例来展示其应用。 #### 二、基本概念 **快速幂**是一种用来高效计算形如 \(a^n\) 的算法,其中 \(a\) 和 \(n\) 分别是基数和指数。该算法的...

    Ackermann函数

    Ackermann函数,这是一个在计算机科学领域中著名的非递归可计算函数,由荷兰数学家皮埃尔·阿克曼在1928年提出。它主要用作展示递归理论中的某些概念,例如递归函数的存在性和复杂度。该函数不是线性的、阶乘的或是...

    快速幂+相关知识点.docx

    - 使用**快速求幂取模**:如果需要计算\(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; } ``` #### 五、快速幂取模 在实际...

    用母函数理论分析递归算法的时间复杂度

    对于非递归算法来说,时间复杂度的分析相对直观,而对于递归算法,由于其自身的调用特性,分析起来更为复杂。递归算法是一种常见的程序设计方法,它通过函数调用自身来解决问题。 递归算法在执行过程中会产生大量的...

    易语言源码易语言二进制递归运算源码.rar

    易语言的核心理念是“易学易用”,它的语法简洁明了,大部分指令和函数都是以中文命名,使得非英语背景的程序员也能轻松掌握。它支持多种编程范式,包括过程式、面向对象以及组件化编程,具有丰富的内置函数和类库,...

    易语言递归算法进制转换源码

    在易语言中,递归算法和进制转换是两个重要的概念,它们在程序设计中有着广泛的应用。 递归算法是一种解决问题的方法,它通过调用自身来解决问题或简化问题。在易语言中,实现递归通常涉及到函数或过程的自调用。...

Global site tag (gtag.js) - Google Analytics