排序数组的平方,为什么sorted()方法比O(n)方法快?

时间:2019-11-28 09:16:38

标签: python algorithm time-complexity onlinejudge

我正在研究leetcode算法问题977. Squares of a Sorted Array.

为什么使用内置方法对提交的邮件进行排序比下面的o(n)遍历方法快?

输入是一个带有整数的排序列表(非降序)。

提交208毫秒的示例:

class Solution:
    def sortedSquares(self, A: List[int]) -> List[int]:
        return sorted(x*x for x in A)

我的260毫秒提交时间:

class Solution:
    def sortedSquares(self, A: List[int]) -> List[int]:
        start = 0
        end = len(A)-1

        B = []

        while start <= end:
            if abs(A[start]) >= abs(A[end]):
                B.append(A[start] * A[start])
                start += 1
            else:
                B.append(A[end] * A[end])
                end -= 1
        B.reverse()
        return B

5 个答案:

答案 0 :(得分:2)

仅仅因为您的算法具有更好的最坏情况运行时间,并不意味着它在实践中会更好。 Python的内置排序方法经过高度优化,因此对于相对较小的C:/Strawberry/perl/lib,其运行时间可以为cnlg(n),而算法为c时,则可以具有非常高的常数{ O(n)代表d。我们不知道您的输入是什么,因此它可能是由10000个元素组成的数组,为此dn仍然比d大得多。

此外,由于您的输入列表是经过排序的(非负数部分),因此在这种情况下,可能会对近乎排序的列表进行一些优化(不确定,从来没有看过Python的sort实现)。

答案 1 :(得分:2)

LeetCode报告的时间是针对您的解决方案进行测试的所有测试用例的总和。作为算法效率的度量,这是非常不可靠的,因为

  • 大多数测试用例都是在很小的投入下
  • 法官本身所花费的间接费用时间是可观的,并且变化很大,因此两次测量之间的适度差异也可能仅取决于该间接费用的随机变化。

有关更多讨论,请参见this answer


因此,如果我们想比较两种算法的性能,最好自己动手做。我做到了这是我的时间,最多可访问5,000个列表(每个平均运行1,000多个):

times 1

这是我的时代,不超过100万个列表的大小(无平均值):

times 2

如您所见,无论大小输入,使用sorted的速度一直都比较快,因此这次LeetCode的比较实际上是正确的(尽管无论如何都是巧合!)。您的解决方案确实是线性时间,并且这些结果与之一致。但是使用sorted的解决方案似乎也花费了线性时间,堆溢出的答案解释了这样做的原因。


我的计时代码如下。时间是使用Python 3.5.2进行测量的,而使用Python 3.8.1可获得类似的结果。

def linear(A):
    start = 0
    end = len(A)-1
    B = []
    while start <= end:
        if abs(A[start]) >= abs(A[end]):
            B.append(A[start] * A[start])
            start += 1
        else:
            B.append(A[end] * A[end])
            end -= 1
    B.reverse()
    return B

def using_sorted(A):
    return sorted(x*x for x in A)

from timeit import timeit
from random import randint

print('n', 'linear', 'using sorted', sep='\t')
for n in range(10000, 1000001, 10000):
    data = sorted(randint(-10*n, 10*n) for i in range(n))
    t1 = timeit(lambda: linear(data), number=1)
    t2 = timeit(lambda: using_sorted(data), number=1)
    print(n, t1, t2, sep='\t')

答案 2 :(得分:1)

您可以对所有内容进行预平方(因此不需要abs(),尤其是不需要重复的abs()调用单个元素):

C = [x*x for x in A]

start = 0
end = len(A)-1

B = []

while start <= end:
    if C[start] >= C[end]:
        B.append(C[start])
        start += 1
    else:
        B.append(C[end])
        end -= 1
B.reverse()
return B

但是我还没有测量。例如,就地预平方可能会更好。

答案 3 :(得分:1)

使用Timsort的Python内置sorted函数在这种情况下也是 O(n)!您的输入数字已排序并平方。让我们看一下三种情况:

  • 如果您的输入仅包含正数,则对它们进行平方将保持排序。 Timsort在O(n)时间内检测到该事件,并且没有执行其他任何操作。
  • 如果您的输入仅包含负数,则平方将使其反向排序。例如,[-5, -4, -2, -1]变为[25, 16, 4, 1]。 Timsort会检测到该情况,并简单地反转该列表。再次计时O(n)。
  • 如果您的输入同时包含负数和正数,那么在平方之后,您将先执行降序运算,然后再执行升序运算。然后,Timsort如此检测到这两种情况,反转下降运行,并对这两个运行进行一次简单合并。再次计时O(n)。

所以...内置sorted和您自己的排序都是O(n)。但是sorted是在C级别实现的,它更快并且可以访问快捷方式,从而为它提供了一个较小的隐藏常量。

答案 4 :(得分:0)

这是因为Python使用Timsort,这是一种基于marge_sort和insert_sort的自适应排序算法。时间复杂度是

  

O(nlogn)

对于python中的Sorted()。

这就是为什么Sorted()比您的工作更快的原因