我看到当人们实施public class Solution {
public double pow(double x, int n) {
if(n == 0)
return 1;
if(n<0){
n = -n;
x = 1/x;
}
return (n%2 == 0) ? pow(x*x, n/2) : x*pow(x*x, n/2);
}
}
时,他们总是在下面使用类似的解决方案。我的困惑是,下面的解决方案比较强力倍数x多次n的优势是什么?
{{1}}
答案 0 :(得分:6)
这种技术正式称为Exponentiation by squaring。它需要的操作少于&#34; flat&#34;通过重复乘法求幂,因此N k 的结果在log 2 k时间内计算。
请注意,虽然此算法的实现通常是递归的,但不一定要这样做。可以使用迭代而不是递归来重写算法,以产生相同的加速:
public static double pow(double x, int n) {
if(n == 0)
return 1;
double y = 1;
while (n > 1) {
if (n%2 == 1) {
y *= x;
}
x *= x;
n /= 2;
}
return x*y;
}
答案 1 :(得分:4)
此算法在O(log(n))时间内运行,而不是在O(n)时间内运行。如果n
是偶数,则通过使用标识x^n = (x^2)^(n/2)
将剩余的工作分成两半。如果n
是奇数,它必须进行标准乘法,但是n
将在下一次迭代时均匀,因此它们最终平均为log(n)时间。
答案 2 :(得分:4)
它更快。
假设您执行pow(3, 8)
。
这会调用pow(9, 4)
。
反过来,这会调用pow(81, 2)
。
然后pow(6561, 1)
。
这是我们的第一个奇怪的力量,所以这次我们6561 * pow(43046721, 0)
,我们得到6561
的最终答案。
(如果pow(6561, 1)
刚刚返回6561
而不是计算43046721
,那可能会更好。
与需要七个的明显解决方案相比,这只需要四次乘法。通常,它会将时间复杂度从O(n)
降低到O(log n)
。这里n
是权力,而不是基础。
答案 3 :(得分:4)
当简单地乘以x * x * x * x * ...
时,这似乎具有对数复杂度,而不是线性复杂度。
最后,它将导致所需的步骤数量减少(对于更高的x,这将是非常重要的)。
例如,简单的线性方法x^8
将导致八个步骤:
step1 = x
step2 = x * step1
step3 = x * step2
step4 = x * step3
step5 = x * step4
step6 = x * step5
step7 = x * step6
step8 = x * step7
return step8
所呈现的版本基本上是这样的:
step1 = x
step2 = step1 * step1 // equal to x * x
step3 = step2 * step2 // equal to x * x * x * x
step4 = step3 * step3 // equal to x * x * x * x * x * x * x * x
return step4
答案 4 :(得分:2)
通过查看这段代码可以很容易地发现,此函数的算法复杂度为 O(log2(n))
,而自然实施会导致(大致) n
操作。
考虑到:
if(n == 0)
return 1;
if(n < 0){
n = -n;
x = 1/x;
}
是常量复杂度(O(1)
),算法中唯一受到您想要提升输入功能影响的部分如下:< / p>
if (n%2 == 0) {
return pow(x*x, n/2);
} else {
return x*pow(x*x, n/2);
}
在任何一种情况下,您都会注意到,通过该方法的每次迭代都会导致另一次迭代,直到n
到达0
。这里也是明显的,使用此算法,n
将以对数的速度减少。
例如,以这种方式计算的2^1024
将导致大约10次迭代:
pow(2, 1024);
\_pow(4, 512);
\_pow(16, 256);
\_pow(256, 128);
\_pow(65536, 64);
\_pow(..., 32);
\_pow(..., 16);
\_pow(..., 8);
\_pow(..., 4);
\_pow(..., 2);
\_pow(..., 1);
...因此总计大约 10 实际操作,而不是 1024 自然实施。