下面,代码的目的是计算整数的幂。我的朋友告诉我,这个算法的时间复杂度是O(log n)。但是,实际上函数调用的次数并不等于logn。例如,power(2,9)调用power函数5次(包括调用power(2,9)),而power(2,8)调用power函数4次(包括调用power(2,8))。然而,8和9所需的位数是相同的,函数调用的数量是不同的。
为什么会发生这种情况?时间复杂度O(log n)
#include <stdio.h>
int power(int a, int n) {
if(n == 0) {
return 1;
}
if(n == 1) {
return a;
}
if (n%2 == 0) {
return power(a*a, n/2);
}else{
return a * power(a, n - 1);
}
}
int main() {
for (int i = 0; i < 15; i++)
printf("pow(%d, %d) = %d\n", 2, i, power(2, i));
return 0;
}
4条答案
按热度按时间ars1skjm1#
你的实现是O(logN)的,但它可以稍微更有效。
注意,在下文中,对数是以2为底的对数。
您有log(n)次
power(a*a,n/2)
调用,以及对 n 中设置的每个位的power(a, n-1)
调用。在 n 中设置的位数至多为log(n)+1。
因此,对
power
的调用次数至多为log(n)+log(n)+1。例如,当 n = 15时,调用序列为log(n)+log(n)+1 = 3+3+1 = 7
下面是一个更高效的实现,它只有log(n)+2次
power
调用。在这种情况下,当 n = 15时的调用序列是
我删除了
if (n == 1)
条件,因为我们可以通过添加一个对power
的调用来避免执行log(n)时间的测试。然后我们有log(n)+2次调用,这比2log(n)+1次调用要好。
68bkxrlz2#
算法保持O(lgN)的原因是,即使对于奇数情况具有额外的调用,因为额外调用的数量由常数限制。在最坏的情况下,N/2 在每次迭代时都是奇数,但这只会使额外调用的数量加倍(常数为2)。也就是说,在最坏的情况下,将有21 g N 个调用来完成算法。
为了更容易地观察到算法是0(lgN),您可以重写函数以始终在每次迭代时将功率减少一半,使得在最坏情况下,仅存在lgN 调用。为了利用尾部递归,可以添加一个函数参数来累加奇数 N 的进位乘数。
尾递归的优点是优化后的代码将被大多数现代C编译器转换为简单的循环。
在线试用!
ehxuflar3#
幂函数有两种基本情况:n = 0和n = 1。
幂函数有两个递归调用。在任何给定的调用中,只进行其中一个。
让我们首先考虑当n是偶数时的情况:在这种情况下,使用n / 2进行递归调用。
如果所有调用都使用这种情况,那么你在每个调用中将n减半,直到达到1。这实际上是
log(n)
调用(基本情况下加1)。另一种情况是,当n是奇数时,n只减1。如果所有的调用最终都使用这个递归调用,那么这个函数将被调用n次;显然不是对数而是线性的。
但是当你从奇数中减去1时会发生什么?它变成了一个偶数。因此,上述令人担忧的线性行为不会发生。
最差情况为:n是奇数,因此使用第二递归调用。现在n是偶数,因此第一次递归调用。现在n是奇数,这个使用第二,…以此类推,直到n为1。在这种情况下,每第二次调用将n减少到n / 2。因此,您需要
2 * log(n)
调用(再加上一个基本情况)。所以是的,这是在
O(log(n))
中。这个算法通常被称为binary exponentiation。pxiryf3j4#
这是另一种表示法: