为什么这个o(n)三向集合不相交算法比o(n ^ 3)版本慢?

时间:2016-10-16 04:19:16

标签: python algorithm complexity-theory

O(n)因为转换列表设置为O(n)时间,得到交集是O(n)时间而len是O(n)

def disjoint3c(A, B, C):
    """Return True if there is no element common to all three lists."""
    return len(set(A) & set(B) & set(C)) == 0

或类似地,应该明显是O(N)

def set_disjoint_medium (a, b, c):
    a, b, c = set(a), set(b), set(c)
    for elem in a:
        if elem in b and elem in c:
            return False
    return True

这个O(n ^ 3)代码:

def set_disjoint_slowest (a, b, c):
    for e1 in a:
        for e2 in b:
            for e3 in c:
                if e1 == e2 == e3:
                    return False
    return True

跑得更快

查看算法1是n ^ 3的时间,算法3是O(n)集代码...算法2实际上是n ^ 2,我们通过在第三个循环开始之前检查不相交来优化算法1 / p>

Size Input (n):  10000

Algorithm One: 0.014993906021118164

Algorithm Two: 0.013481855392456055

Algorithm Three: 0.01955580711364746

Size Input (n):  100000

Algorithm One: 0.15916991233825684

Algorithm Two: 0.1279449462890625

Algorithm Three: 0.18677806854248047

Size Input (n):  1000000

Algorithm One: 1.581618070602417

Algorithm Two: 1.146049976348877

Algorithm Three: 1.8179030418395996

2 个答案:

答案 0 :(得分:4)

这些评论澄清了关于Big-Oh符号的内容。所以我将从测试代码开始。

这是我用来测试代码速度的设置。

import random

# Collapsed these because already known
def disjoint3c(A, B, C):
def set_disjoint_medium (a, b, c):
def set_disjoint_slowest (a, b, c):

a = [random.randrange(100) for i in xrange(10000)]
b = [random.randrange(100) for i in xrange(10000)]
c = [random.randrange(100) for i in xrange(10000)]

# Ran timeit.
# Results with timeit module.
1-) 0.00635750419422
2-) 0.0061145967287
3-) 0.0487953200969

现在结果显示,O(n^3)解决方案比其他解决方案运行 8 慢。但对于这样的算法来说,这仍然很快(在测试中速度更快)。 为什么会这样?

由于您使用的是中等和最慢的解决方案,因此只要检测到公共元素就完成代码的执行。因此,代码的完全复杂性没有实现。一旦找到答案就会中断。为什么最慢的解决方案几乎和测试中的其他解决方案一样快?可能是因为它找到了更接近列表开头的答案。

要测试此功能,您可以创建这样的列表。亲自尝试一下。

a = range(1000)
b = range(1000, 2000)
c = range(2000, 3000)

现在时间之间的真正差异将是显而易见的,因为最慢的解决方案必须运行直到它完成所有迭代,因为没有共同的元素。

所以这是最坏情况最佳情况表现的情况。

不是编辑问题的一部分:那么,如果您希望保持找到早期常见事件的速度,但又不想增加复杂性,该怎么办?我为此做了一个粗略的解决方案,也许更有经验的用户可以建议更快的代码。

def mysol(a, b, c):
    store = [set(), set(), set()]

    # zip_longest for Python3, not izip_longest.
    for i, j, k in itertools.izip_longest(a, b, c):
        if i: store[0].add(i)
        if j: store[1].add(j)
        if k: store[2].add(k)

        if (i in store[1] and i in store[2]) or (j in store[0] and i in store[2]) or (k in store[0] and i in store[1]):
            return False
    return True

在此代码中基本上要做的是,您避免在开始时将所有列表转换为集合。相反,同时遍历所有列表,向集合添加元素,检查常见事件。所以现在,你保持找到早期解决方案的速度,但对于我所展示的最坏情况,它仍然很慢。

对于速度,在最坏的情况下,这比前两个解决方案慢3-4倍。但运行速度比随机列表中的解决方案快4-10倍。

注意:您在三个列表中找到所有常见元素(在第一个解决方案中)无疑意味着理论上存在更快的解决方案。因为你只需要知道 if ,甚至只有一个共同元素,而且知识就足够了。

答案 1 :(得分:0)

O符号忽略所有常数因素。因此它只会回答无限数据集。对于任何有限集合,它只是一个经验法则。

对于Python和R等解释型语言,常数因子可能非常大。他们需要创建和收集许多对象,这些对象都是O(1)但不是免费的。因此,遗憾的是,看到几乎等效代码的100倍性能差异是相当普遍的。

其次,第一个算法计算所有公共元素,而其他算法在第一个上失败。如果您对algX(a,a,a)进行基准测试(是的,所有三个组都相同)那么它将比其他组做更多的工作!

我不会惊讶地看到基于排序的O(n log n)算法非常具有竞争力(因为排序通常非常优化)。对于整数,我会使用numpy数组,并尽可能避免python解释器,你可以非常快。虽然numpys in1dintersect可能会给你一个O(n ^ 2)或O(n ^ 3)算法,但只要你的集合通常是不相交的,它们最终可能会更快。

另请注意,在您的情况下,这些集合不一定是成对不相交的...... algX(set(),a,a)==True