仅使用递归怎么可能达到O(log n)幂函数a ^ n?

时间:2019-12-18 08:20:01

标签: c recursion

下面,代码的目的是计算整数的幂。 我的朋友告诉我,该算法的时间复杂度为O(log n)。 但是,实际上函数调用的数量不等于logn。 例如,power(2,9)调用power函数5次(包括调用power(2,9)),而power(2,8)调用4次power函数(包括调用power(2,8)。 尽管如此,8和9所需的位数相同,函数调用的位数却不同。

为什么会这样?这真的是O(log n)算法吗?

DATEVALUE

3 个答案:

答案 0 :(得分:3)

您的实现为O(logN),但可以使其效率更高。

请注意,此后,日志是基于2的日志。

您有power(a*a,n/2)的log(n)个调用,并且 n 中设置的每个位都有一个power(a, n-1)的调用。

n 中设置的位数最多为log(n)+1。

因此,对power的呼叫数量最多为log(n)+ log(n)+1。例如,当 n = 15时,调用顺序为

power(15), power(14), power(7), power(6), power(3), power(2), power(1)

log(n)+ log(n)+1 = 3 + 3 + 1 = 7

这是一种更高效的实现,仅具有power的log(n)+2次调用。

int power(int a, int n) {
  if(n == 0) {
    return 1;
  }
  if (n&1 == 0) {
    return power(a*a, n/2);
  }else{
    return a * power(a*a, n/2);
  }
}

在这种情况下,当 n = 15时的呼叫顺序为

power(15), power(7), power(3), power(1), power(0)

我删除了if (n == 1)条件,因为我们可以通过向power添加一个调用来避免在log(n)时间执行的测试。

然后我们进行log(n)+2次上电,比2log(n)+1好。

答案 1 :(得分:1)

幂函数有两种基本情况:n = 0和n = 1。

幂函数有两个递归调用。在任何给定的呼叫中仅产生其中一个。

让我们首先考虑n为偶数的情况:在这种情况下,递归调用是使用n / 2进行的。

如果所有通话都使用这种情况,那么您每次通话都将n减半,直到达到1。这实际上是log(n)通话(基本情况加1)。

另一种情况,当n为奇数时,仅将n减1。如果所有调用最终都将使用此递归调用,则该函数将被调用n次;否则,该函数将被调用n次。显然不是对数而是线性。

但是,当您从奇数中减去一个时,会发生什么?变成偶数。因此,上面提到的担心的线性行为就不会发生。

最坏的情况是:n为奇数,因此使用第二个递归调用。现在n是偶数,因此是第一个递归调用。现在n为奇数,使用秒,...依此类推,直到n为1。在这种情况下,每隔第二个调用将n减少为n /2。因此,您需要2 * log(n)个调用(对于基本情况,再加上一个)。

是的,它在O(log(n))中。这种算法通常称为binary exponentiation

答案 2 :(得分:0)

即使对于奇数情况下有额外的调用,该算法仍保持ο(lg N )的原因是因为额外调用的数量受常数限制。在最坏的情况下, N / 2 在每次迭代中都是奇数,但这只会使额外调用的次数增加一倍(常数为2)。也就是说,在最坏的情况下,将有2lg N 个调用来完成算法。


为了更容易地观察到该算法为Ο(lg N ),您可以重写函数以始终在每次迭代时将功耗降低一半,因此在最坏的情况下,只有lg N 个电话。要利用尾部递归,可以添加一个函数参数,以从奇数 N 中累加进位乘数。

id <- c(1,1,1,1,2,2,3,3,3,3,4,4,4)
Affect <- c(0.8, 0.5, NA, 0.8, 0.2, 0.1, 0.7, 1.1, 0.9, 0.5, 0.3, NA, 0.9)
Paranoia <-  c(0.9, 0.6, 0.4, 0.2, 0.1, NA, 0.3, 0.1, 0.9, 1.5, 0.4, 0.1, 0.6)
both <- data.frame(id, Affect, Paranoia)

尾部递归的优点是,大多数现代C编译器会将优化的代码转换为简单的循环。

Try it online!