我不擅长确定时间和内存复杂度,希望有人能帮助我。
这里有一个算法,但我不确定它的时间和内存复杂度是什么。
Function sample(k)
IF k < 2
Return 0
Return 1 + sample(k/2)
它的时间和空间复杂度是多少,为什么?
谢谢
我不擅长确定时间和内存复杂度,希望有人能帮助我。
这里有一个算法,但我不确定它的时间和内存复杂度是什么。
Function sample(k)
IF k < 2
Return 0
Return 1 + sample(k/2)
它的时间和空间复杂度是多少,为什么?
谢谢
sample(k) )
sample(k/2) )
sample(k/4) )
... ) - n = number of calls
sample(4) )
sample(2) )
sample(1) )
这样每次递归调用时减半的方式将导致呈现出一个 对数 次数的调用。
n = log(k)
time complexity = A*log(k) + B
对于一些常量A和B,它们反映了进行递归调用和比较/分割所需的实际时间成本。同样地:
space complexity = C*log(k) + D
对于递归的空间成本和变量存储,我们需要合适的常数C和D。
在这种分析中,我们主要关注渐进复杂度,也就是说,我们并不关心常数,因为它们反映了运行算法的机器的细节,我们真正想知道的是曲线的形状(随着k的增加)。如果您遵循使用大O符号编写复杂度的规则,您将得到以下结果:
space complexity = time complexity = O(log(k))
如我之前所说,内存复杂度取决于需要多大的数据结构来计算解决方案,那么你可能会问:这个函数没有使用数据结构,为什么会使用 log(k) 的内存呢?
简短回答: 您需要存储 log(k)
种不同的参数 k
值,每次递归调用时都要存储一个值。
详细回答: 这里通过函数调用机制(我们通过递归利用)使用了一个隐式的数据结构,其名称为 调用栈。每次调用 sample(k)
时,都会创建一个新的堆栈帧,并将一些值推送到堆栈上:参数 k
的本地值、返回地址和其他与实现有关的信息。这样,每个递归调用形成一个在堆栈上的“层”,其中存储着它的本地信息。整个情况最终看起来像这样:
----------- < top of stack
| k = 1 |
| ... | < stack frame for sample(1)
|---------|
| k = 2 |
| ... | < stack frame for sample(2)
|---------|
...
|---------|
| k = p/2 |
| ... | < stack frame for sample(p/2)
|---------|
| k = p |
| ... | < stack frame for sample(p)
|---------|
| | < stack frame for main() or whatever
initially called sample(p)
(we don't count this one)
(我在这里区分了初始参数值p
和每个递归调用时k
的值,希望避免混淆)
需要注意的主要是,由于有n = log(k)
次递归调用,因此有n
个这样的堆栈帧。每个堆栈帧具有恒定的大小,因此空间复杂度为O(log(k))
。
你真正关注的是log_2(k),以2为底数的对数。要改变底数,必须乘以一个常数。由于我们无论如何都要乘以常数,因此O(log(n))、O(ln(n))和O(log_2(n))都是相同的。
那么为什么上述方法具有以2为底数的对数复杂度呢?每次调用时,你都将k除以2。如果你倒过来,每次调用时就会乘以2。乘以2是2^n,而log_2(n)恰好是其倒数。
也许画一个二叉树会有所帮助:一个有n个节点的树的高度为log_2(n),一个高度为n的树有2^n个节点。