列表理解的大O在条件中调用max:O(n)或O(n ^ 2)?

时间:2017-08-22 22:18:56

标签: python algorithm performance big-o list-comprehension

Q值。编写一个返回数组中第二大数字的算法

a = [1, 2, 3, 4, 5]
print(max([x for x in a if x != max(a)]))
>> 4

我试图弄清楚这个算法是如何工作的,以及pythons内部魔术是否会像写一个线性算法一样有效,这个算法只是循环遍历列表a一次并存储最高和第二高的值。

如果我错了,请纠正我:

  • max(a)的调用将是O(n)

  • [x for x in a]也是O(n)

python是否足够智能来缓存max(a)的值,或者这意味着算法的列表推导部分是O(n ^ 2)?

然后最后的max([listcomp])将是另一个O(n),但这只会在理解完成后运行一次,所以最终的算法将是O(n ^ 2)?

内部是否有任何花哨的业务会缓存max(a)值并导致此算法的运行速度比O(n ^ 2)快?

2 个答案:

答案 0 :(得分:6)

找出答案的简单方法是计时。考虑这个时间码:

for i in range(1, 5):
    a = list(range(10**i))
    %timeit max([x for x in a if x != max(a)])
17.6 µs ± 178 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
698 µs ± 14.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
61.6 ms ± 340 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
6.31 s ± 167 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

每次将元素数乘以10,运行时间增加100.这几乎肯定是O(n**2)。对于O(n)算法,运行时将随元素数量线性增加:

for i in range(1, 6):
    a = list(range(10**i))
    max_ = max(a)
    %timeit max([x for x in a if x != max_])
4.82 µs ± 27.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
29 µs ± 161 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
262 µs ± 3.89 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
2.42 ms ± 13 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
24.9 ms ± 231 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

但我不确定算法是否真的能满足要求。考虑列表a=[1,3,3],即使heapq模块告诉我第二大元素是3(不是1 - 您的算法返回的内容):

import heapq

>>> heapq.nlargest(2, [1,3,3])[0]
3

答案 1 :(得分:3)

  

python是否足够聪明,可以缓存max(a)或will的值   这意味着列表理解部分算法是O(n^2)

不,因为,正如MSeifert在评论中所说,python不会对a做出假设,因此不会缓存每次重新计算的max(a)的值。

您可能需要考虑一个跟踪一次传递中最大的两个项目的实现。您需要编写显式的for循环并执行此操作。这是来自GeeksForGeeks的有用链接(我推荐)。

或者,您可以考虑多个遍历仍然在复杂性上呈线性的遍历。

In [1782]: a = [1, 2, 3, 4, 5]

In [1783]: max(set(a) - {max(a)}) # 3 linear traversals
Out[1783]: 4

这里有改进的余地,但正如我所说的,没有什么比明确的for循环方法更好。