使用递归的幂函数
我必须用Java编写一个power方法。 它接收两个整数,如果它们是正数或负数则无关紧要。 它应该具有O(logN)
复杂性。 它还必须使用递归。 我当前的代码得到两个数字,但我保持输出的结果为零,我无法弄清楚为什么。
import java.util.Scanner; public class Powers { public static void main(String[] args) { float a; float n; float res; Scanner in = new Scanner(System.in); System.out.print("Enter int a "); a = in.nextFloat(); System.out.print("Enter int n "); n = in.nextFloat(); res = powers.pow(a, n); System.out.print(res); } public static float pow(float a, float n) { float result = 0; if (n == 0) { return 1; } else if (n 0) { result = result * pow(a, n - 1); } return result; } }
让我们从一些数学事实开始:
- 对于正n,aⁿ=a⨯a⨯…⨯an次
- 对于负n,aⁿ=⅟a – ⁿ=⅟(a⨯a⨯…⨯a)。 这意味着一个不能为零。
- 对于n = 0,aⁿ= 1,即使a为零或负。
让我们从积极的n案例开始,并从那里开始工作。
由于我们希望我们的解决方案是递归的,我们必须找到一种方法来定义基于较小n的a,并从那里开始工作。 人们通常认为递归的方法是尝试找到n-1的解决方案,并从那里开始工作。
事实上,由于a mat =a⨯(aⁿ-1)在数学上是正确的,所以天真的方法与你创造的方法非常相似:
public static int pow( int a, int n) { if ( n == 0 ) { return 1; } return ( a * pow(a,n-1)); }
但是,这种复杂性是O(n)。 为什么? 因为对于n = 0,它不进行任何乘法运算。 对于n = 1,它进行一次乘法。 对于n = 2,它调用pow(a,1),我们知道它是一个乘法,并将它乘以一次,所以我们有两次乘法。 每个递归步骤都有一个乘法,有n个步骤。 所以它是O(n)。
为了得到这个O(log n),我们需要将每个步骤应用于n的一小部分而不仅仅是n-1。 在这里,有一个数学事实可以帮助我们:a n 1 + n 2 = a n 1 a a n 2 。
这意味着我们可以将aⁿ计算为n / 2⨯an / 2 。
但如果n是奇数,会发生什么? 像a⁹这样的东西将是4.5⨯a4.5。 但我们在这里讨论整数权力。 处理分数是完全不同的事情。 幸运的是,我们可以将其表述为a⨯a⁴⨯a⁴。
因此,对于偶数使用n / 2⨯an / 2 ,对于奇数,使用a⨯an / 2⨯an / 2 (整数除法,给出9/2 = 4)。
public static int pow( int a, int n) { if ( n == 0 ) { return 1; } if ( n % 2 == 1 ) { // Odd n return a * pow( a, n/2 ) * pow(a, n/2 ); } else { // Even n return pow( a, n/2 ) * pow( a, n/2 ); } }
这实际上给了我们正确的结果(对于正n,即)。 但事实上,这里的复杂性是O(n)而不是O(log n)。 为什么? 因为我们两次计算权力。 这意味着我们实际上在下一级别调用它4次,在下一级别调用8次,依此类推。 递归步骤的数量是指数级的,所以这取消了我们通过将n除以2所做的假设保存。
但事实上,只需要进行小的修正:
public static int pow( int a, int n) { if ( n == 0 ) { return 1; } int powerOfHalfN = pow( a, n/2 ); if ( n % 2 == 1 ) { // Odd n return a * powerOfHalfN * powerOfHalfN; } else { // Even n return powerOfHalfN * powerOfHalfN; } }
在这个版本中,我们只调用一次递归。 因此,我们从64,16,8,4,2,1和非常快速地获得64的幂。 每一步只有一到两次乘法,而且只有六个步骤。 这是O(log n)。
所有这些的结论是:
- 为了得到一个O(log n),我们需要递归,它在每一步上只有n的一小部分,而不仅仅是n – 1或n – 任何东西。
- 但这一部分只是故事的一部分。 我们需要注意不要多次调用递归,因为在一个步骤中使用几个递归调用会产生指数复杂性,使用一小部分n取消。
最后,我们准备好照顾负数。 我们只需得到倒数⅟a – ⁿ。 有两件重要的事情需要注意:
- 不允许除以零。 也就是说,如果你得到a = 0,你不应该执行计算。 在Java中,我们在这种情况下抛出exception。 最合适的现成例外是IllegalArgumentException。 这是一个RuntimeException,因此您不需要在方法中添加
throws
子句。 如果您在读取参数时在main
方法中捕获它或阻止这种情况发生将会很好。 - 你不能再返回一个整数(实际上,我们应该使用
long
,因为我们使用int
进行整数溢出以获得相当低的幂) – 因为结果可能是小数。
所以我们定义方法,使它返回double。 这意味着我们还必须修复powerOfHalfN
的类型。 这是结果:
public static double pow(int a, int n) { if (n == 0) { return 1.0; } if (n < 0) { // Negative power. if (a == 0) { throw new IllegalArgumentException( "It's impossible to raise 0 to the power of a negative number"); } return 1 / pow(a, -n); } else { // Positive power double powerOfHalfN = pow(a, n / 2); if (n % 2 == 1) { // Odd n return a * powerOfHalfN * powerOfHalfN; } else { // Even n return powerOfHalfN * powerOfHalfN; } } }
请注意,处理负数n的部分仅用于递归的顶级。 一旦我们递归地调用pow()
,它总是带有正数,并且符号在达到0之前不会改变。
这应该是你锻炼的适当解决方案。 但是,我个人不喜欢最后的那个,所以这里是另一个版本。 你能说出为什么这样做吗?
public static double pow(int a, int n) { if (n == 0) { return 1.0; } if (n < 0) { // Negative power. if (a == 0) { throw new IllegalArgumentException( "It's impossible to raise 0 to the power of a negative number"); } return 1 / pow(a, -n); } else { // Positive power double powerOfHalfN = pow(a, n / 2); double[] factor = { 1, a }; return factor[n % 2] * powerOfHalfN * powerOfHalfN; } }
注意:
float result = 0;
和
result = result * pow( a, n+1);
这就是你得到零结果的原因。 相反,建议像这样工作:
result = a * pow( a, n+1);
除了将result
初始化为0的错误外,还有一些其他问题:
- 你对负
n
计算是错误的。 记住a^n == 1/(a^(-n))
。 - 如果n不是整数,则计算复杂得多,您不支持它。 如果您不需要支持,我不会感到惊讶。
- 为了实现
O(log n)
性能,您应该使用分而治之的策略。 即a^n == a^(n/2)*a^(n/2)
。
一个好的规则是远离键盘,直到algorythm准备好。 你做了什么显然是O(n)。
正如Eran建议的那样,为了获得O(log(n))复杂度,你必须在每次迭代时将n除以2。
结束条件:
- n == 0 => 1
- n == 1 => a
特殊情况 :
- n <0 => 1. / pow(a,-n)//注意1.获得双倍…
正常情况:
- m = n / 2
- result = pow(a,n)
- result = resul * resul //避免计算两次
- 如果n是奇数(n%2!= 0)=>结果* = a
这个algorythm在O(log(n))中 – 由你自己编写正确的java代码
但是你被告知:n 必须是整数(正数为负,但整数 )
这是一个不那么容易混淆的方式,至少如果你不担心额外的乘法。 :
public static double pow(int base,int exponent) { if (exponent == 0) { return 1; } if (exponent < 0) { return 1 / pow(base, -exponent); } else { double results = base * pow(base, exponent - 1); return results; } }
import java.io.*; import java.util.*; public class CandidateCode { public static void main(String args[] ) throws Exception { Scanner sc = new Scanner(System.in); int m = sc.nextInt(); int n = sc. nextInt(); int result = power(m,n); System.out.println(result); } public static int power(int m, int n){ if(n!=0) return (m*power(m,n-1)); else return 1; } }
试试这个:
public int powerN(int base, int n) {return n == 0 ? 1 : (n == 1 ? base : base*(powerN(base,n-1)));