Python:创建大小为 n^2 的元组的时间和空间复杂度
Posted
技术标签:
【中文标题】Python:创建大小为 n^2 的元组的时间和空间复杂度【英文标题】:Python: Time and space complexity of creating size n^2 tuples 【发布时间】:2018-09-04 06:19:33 【问题描述】:这是我学校过去一年的期中论文中的一个问题。下面附上一张图表,显示机器人将如何移动,来自同一张纸。橙色部分说明了我的担忧。
基本上,机器人在遇到左侧未访问的方格时会向前移动并左转。
机器人横穿 3 号网格的指令顺序为: ('F','T','F','T','F','F','T','F','F','T','F','F',' F') 其中“F”表示向前移动一格,“T”表示向左转 90 度。请注意,最后一条指令会导致机器人退出网格。 函数 gen_seq 将网格的大小作为输入,并返回一系列指令供机器人横穿网格。指令序列是一个包含字符串“F”和“T”的元组,代表前进和转向命令。
提供函数 gen_seq 的递归或迭代实现。 提示:召回 int 可以与 tuple 相乘。
根据您的实施时间和空间说明增长顺序并解释您的答案。
这些是评分方案中建议的答案。
def gen_seq(n): # recursive
if n == 1:
return ('F',)
else:
side = ('T',) + (n-1)*('F',)
return gen_seq(n-1) + side + side + ('F',)
def gen_seq(n): # iterative
seq = ('F',)
for i in range(2, n+1):
side = ('T',) + (n-1)*('F',)
seq += side + side + ('F',)
return seq
时间:O(n^3)。在每个函数调用(递归)或循环(迭代)中,都会创建一个螺旋的每个“层”的路径长度的新元组。由于螺旋的长度是 n^2,并且有 n 个函数调用或循环运行 n 次,所以总时间是 n^2*n = O(n3)。 换句话说,它是平方和:1^2+2^2+3^2+: : :+n^2 = O(n^3)
空间:O(n^2)。一天结束时,创建并返回一个大小为 n^2 的新元组。
1)我是否正确推断形成元组的时间复杂度的推导似乎是每次递归/迭代后更新元组的长度之和。
如果我想形成一个大小为 n^2(拉直螺旋的大小)的元组,首先必须形成 1^2,然后是 2^2…n^2,导致上述平方和。
我看到一篇关于字符串而不是元组的相关帖子,在这种情况下时间= 1+2+…n=n^2,这支持了我的推断。
Time complexity of string concatenation in Python
2)对于涉及切片/连接的递归函数的空间复杂度,空间等于它们的时间,在这种情况下为 O(n^3),我说是否正确。我对这种情况的解释是:由于有 n 个递归占用了堆栈上的空间,并且每个递归都由串联形成一个长度为 n^2 的新元组(此处没有看到切片),空间将为 O(n *n^2)。
我还认为 O(n^2) 的建议空间仅适用于没有观察到堆栈帧并且空间中仅包含最终元组 (n^2) 的长度的迭代解决方案。
【问题讨论】:
你有什么问题? @jhpratt 它写在橙色部分,编号 您的实际问题是什么?那个 android 移动方案似乎是无关的——一个合适的解决方案是使用生成器而不是生成大量的元组。您不确定您的 O 计算吗?如果是这样,你能让你的问题更清晰更短吗?令人困惑的是,您担心“时间复杂度的推导”,“似乎是什么”,“正确的[...]说什么” ",切片与非切片情况的关系等等。 @MisterMiyagi 关于正确的解决方案,由于我在一个基本的编程模块中,我们坚持使用原始方法。是的,我不确定我的 O 计算,因为我没有通过适当的算法分析课程,我们主要依靠直觉。我不知道如何在不削减细节的情况下缩短它(1)为什么我有这个问题,肯定需要考试 qns 的来源。 (2)如果我不为我的答案提供解释……有人会问我是如何得出答案的。 (3)实际的qns是橙色部分的is it correct qns
,提供背景。
【参考方案1】:
TLDR:您的时间复杂度是正确的,尽管递归 gen_seq
的 O(n^3) 空间过于悲观(它仍然更加浪费)。请注意,最佳静态解决方案是 O(n^2),因为这是答案的大小。如果不需要静态答案,空间复杂度可以降低到 O(1)。
让我们从建立一些基本的复杂性开始。以下适用于时间和空间复杂度:
创建一个字符字面量是 O(1)。 创建一个大小为n
的元组是O(n)。
创建一个空元组或单元素元组是 O(1)。
连接两个长度为n
和m
的元组是O(n+m)。
连接两个长度为n^2
和m
的元组,为O(n^2+m) = O(n^2)。
迭代:
def gen_seq(n): # iterative
seq = ('F',)
for i in range(2, n+1):
side = ('T',) + (i-1)*('F',) # note: `i-1` instead of `n-1`
seq += side + side + ('F',)
return seq
复杂性的关键点是:
range(const, n+1)
循环是 O(n) 时间 复杂度和 O(1) 空间。
side
在 i->n 的大小为 n 时重新构造。空间被重复使用,最大 O(n) 空间。所有 n 次迭代都消耗时间,O(n*n) = O(n^2) 时间。
seq
在所有 n 次迭代中都与一个 n 元组连接。空间被重复使用,最大 O(n*n) = O(n^2) 空间。所有 n 次迭代都消耗时间,因为 O(n*n^2) = O(n^3) 时间。
最大复杂度获胜,因此迭代使用O(n^2) 空间 和O(n^3) 时间。
递归:
def gen_seq(n): # recursive
if n == 1:
return ('F',)
else:
side = ('T',) + (n-1)*('F',)
return gen_seq(n-1) + side + side + ('F',)
复杂性的关键点是:
递归从 n->1 开始重复,意思是 O(n) 时间。side
以 n 大小重新构造。空间没有被重复使用,因为每个side
都是在之前递归构造的,最大为O(n*n) = O(n^2)空间。所有 n 次迭代都消耗时间,O(n*n) = O(n^2) 时间。
return
值在所有 n 次迭代中与一个 n 元组连接。空间被重用,因为每个return
值都是在递归之后构造的,最大为O(n*n) = O(n^2)空间。所有 n 次迭代都消耗时间,因为 O(n*n^2) = O(n^3) 时间。
最大复杂度获胜,因此迭代使用O(n^2) 空间 和O(n^3) 时间。
时间复杂度的限制是每一步的结果必须在下一步重复。在 Python 中,这可以使用生成器来规避 - 这允许您返回中间结果并继续生成更多结果:
def gen_seq(n):
yield 'F'
for i in range(1, n):
yield 'T'
yield from ('F' for _ in range(i))
yield 'T'
yield from ('F' for _ in range(i))
seq = tuple(gen_seq(m))
复杂性的关键点是:
range(n)
循环是 O(n) 时间 复杂度和 O(1) 空间。
yield from ... range(i)
循环是 O(n) 时间和 O(1) 空间。空间重用将其保留在 O(1) 空间。重复 n 次得到 O(n*n) = O(n^2) 时间。
通过tuple
一次连接所有结果是 O(n^2 * 1) = O(n^2) 空间。
最大复杂度获胜,因此迭代使用 O(n^2) 空间和 O(n^2) 时间。如果结果不存储直接使用,则只使用 O(1) 空间。
【讨论】:
以上是关于Python:创建大小为 n^2 的元组的时间和空间复杂度的主要内容,如果未能解决你的问题,请参考以下文章