让我们从一些数学事实开始:
对于正n,aⁿ=a⨯a⨯...⨯a次
对于负n,aⁿ=⅟a - ⁿ=⅟(a⨯a⨯...⨯a)。这意味着a不能为零。
对于n = 0,aⁿ= 1,即使a为零或负数。
所以让我们从积极的n案例开始,然后从那里开始工作。
由于我们希望我们的解决方案是递归的,我们必须找到一种方法来定义基于较小n的a,并从那里开始工作。人们通常认为递归的方法是尝试找到n-1的解决方案,并从那里开始工作。
事实上,由于aⁿ=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⨯an 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中,我们在这种情况下抛出异常。最合适的现成例外是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之前不会改变。
这应该是适合您锻炼的解决方案。但是,我个人不喜欢最后的if,所以这是另一个版本。你能说出为什么会这样做吗?
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;
}
}