我不擅长确定时间和记忆的复杂性,如果有人可以帮助我,我会很感激。
我有一个算法,在这里,我不确定它的时间和记忆复杂性是什么。
Function sample(k)
IF k < 2
Return 0
Return 1 + sample(k/2)
它的时间和内存复杂性是什么?为什么?
由于
答案 0 :(得分:19)
确定时间和内存复杂性等于计算运行算法时使用了多少这两种资源,并查看这些数量如何随输入大小而变化( k 在这种情况下)改变。
时间将取决于每条指令的评估次数,空间将取决于计算解决方案所需的数据结构的大小。
在这个特定的场景中,你正在看一个递归算法,所以基本上这涉及计数1)进行了多少递归调用,以及2)为每个调用完成了多少工作。
由于每次调用输入减半,调用序列将如下所示:
sample(k) )
sample(k/2) )
sample(k/4) )
... ) - n = number of calls
sample(4) )
sample(2) )
sample(1) )
以这种方式减少每次递归调用会导致对数次调用。
n = log(k)
在每次调用时,我们在调用堆栈中存储常量数量的变量,并执行一定量的工作(操作)。这源于这样一个事实:每次调用中的变量数量和比较/加法/除法不会随着 k 更大而变大。
总时间复杂度是呼叫次数乘以每次呼叫完成的工作量,因此
time complexity = A*log(k) + B
对于某些常量A和B,它们分别反映了进行递归调用和进行比较/除法的实际时间成本。同样:
space complexity = C*log(k) + D
对于适当的常数C和D,分别表示递归和变量存储的空间成本。
现在在这种分析中,我们主要关注的是渐近复杂度,即我们并不关心常量,因为它们反映了运行算法的机器的细节,我们真的想知道它的形状曲线( k 变大)。如果您遵循使用Big-Oh表示法编写复杂性的规则,您将得出结果:
space complexity = time complexity = O(log(k))
正如我之前所说,内存复杂性取决于数据结构计算解决方案所需的大小,因此您可能会问:此函数中没有使用数据结构,因此log(k)在哪里记忆力去了吗?
简答:您必须存储log(k)
个参数k
的不同值,每个递归调用一个。
详细解答:函数调用机制(我们通过递归利用)在这里使用隐式数据结构,其名称为 call stack 的。每次调用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))
。
答案 1 :(得分:0)
你真的在看log_2(k),以2为底的对数。要改变基数,你必须乘以常数。而且由于我们无论如何都乘以常数,O(log(n)),O(ln(n))和O(log_2(n))都是相同的。
那么为什么上述方法与base 2具有对数复杂度?你在每次通话时将k分成两半。如果你倒退,你会在每次通话时乘以2。乘以2是2 ^ n,log_2(n)正好相反。
如果你绘制一个二叉树,它可能会有所帮助:一个有n个节点的树的高度为log_2(n),一个高度为n的树有2 ^ n个节点。