以下函数的运行时间复杂度是O(1)
吗?
int pow(int a, int n) {
if (n == 0) {
return 1;
}
if (n % 2 == 1) {
return pow(a, n / 2) * pow(a, n / 2) * a;
} else {
return pow(a, n / 2) * pow(a, n / 2);
}
}
我受到了这种印象,因为代码中只有if语句,没有循环。我之前从未使用过Big-O和递归,我在网上找不到任何好的资源。
答案 0 :(得分:4)
您的函数的运行时为O(n),但可以很容易地修改它以在时间O(log n)中运行。
我们可以通过很多方式看到这一点。首先,我们可以计算正在进行的递归调用的总数,因为每个递归调用都会执行O(1)工作。想象一下,例如,我们将pow(a,8)称为某个数字a。然后
这意味着有
总的来说,这是1 + 2 + 4 + 8 + 16 = 31总的来电。
现在,假设我们称之为pow(a,16)。这将触发对pow(a,8)的两次调用(总共62次递归调用),以及一次初始调用pow(a,16)以进行总共63次递归调用。如果我们调用pow(a,32),我们将两次调用pow(a,16)(总共126次递归调用),另外一次调用pow(a,32),总共127次递归调用。更一般地说,似乎我们称之为pow(a,n),我们会得到4n - 1次调用,这将是O(n)。
我们实际上可以正式证明这一点。设C(n)是对大小为n的输入进行的调用次数。注意
C(0)= 1。 C(n)= 2C(n / 2)+ 1
这种复发通过主定理解决了O(n)。
请注意,每个单独的递归调用都在做很少的工作。杀死我们的是这样一个事实:有很多完整的递归调用,这些调用会使这些调用累加起来。但是,虽然有很多总递归调用,但很少有唯一的递归调用。因此,请考虑代码的这种变化:
int pow(int a, int n) {
if (n == 0) return 1;
int halfPow = pow(a, n / 2);
if (n % 2 == 0) return halfPow * halfPow;
else return halfPow * halfPow * a;
}
此代码缓存正在进行的递归调用的值,因此它每次都会触发一次调用。结果,每次调用完成的工作仍然是O(1),但在递归中没有更多的分支。然后,因为每个递归调用的大小是原始的的一半,并且因为每个级别只有一个调用,所以运行时可以运行到O(log n),您可以使用Master确认定理。
一般来说,要警惕“我们不断削减一半的形式”的论据,因此整体工作最终成为O(log n)。这可能是真的,但是你在每一步所做的工作量对于确定运行时也非常非常重要,正如你在这里看到的那样。
答案 1 :(得分:2)
让我们分解这里发生的事情。实际上是O(n)。对于每次调用pow(),都有两种选择:
因此,在每次调用pow()时,将n减少n / 2,可以在每一步上以指数方式增加问题空间。递归的第一步是两次调用pow,第二步是2 ^ 2,第三步是2 ^ 3等。对于n = 16,将有pow(...,16),pow(...,8),pow( ...,4),pow(...,2),pow(...,1),pow(...,0),其中2 ^ 0调用pow为n = 16,2 ^ 1为n对于n = 4,为2 ^ 2,对于n = 2,为2 ^ 3,对于n = 1,2 ^ 4,对于n = 0,为2 ^ 5。
因此,我们称之为pow log(n)次,但pow调用的每次迭代都会使上一步中的调用次数增加一倍。这意味着我们有O(n)
答案 2 :(得分:1)
不,因为它涉及递归和分支。其时间复杂度为O(n)
,其空间复杂度为O(log n)
。
您将获得O(log n)
时间复杂度:
int pow (int a, int n) {
if(n == 0) { return 1; }
int halfpow = pow(a,n/2);
return halfpow * halfpow * (n % 2 == 1 ? a : 1);
}