确定此函数的最坏情况时间复杂度?

Determining worst-case time-complexity of this function?

我目前正在研究 Richard Hammack 的 'Book of Proof' 中的集合,并且我正在尝试将集合实现为幂(更具体地说,是集合的笛卡尔幂)。示例如下:
假设我设置了 A = {0, 1},那么
A2 = {0, 1} x {0, 1} = {(0, 0), (0, 1), (1, 0), (1, 1)}

我成功实现了一个通用函数,可以将集合提升到我指定的任何幂。代码如下:

def add_to_tuple(tup, a):
    tup = list(tup)
    tup.append(a)
    return tuple(tup)


def multiply(A, B):
    return_set = []
    for a in A:
        for b in B:
            if type(a) is tuple and type(b) is tuple:
                return_set.append(a + b)
            elif type(a) is not tuple and type(b) is tuple:
                return_set.append(add_to_tuple(b, a))
            elif type(a) is tuple and type(b) is not tuple:
                return_set.append(add_to_tuple(a, b))
            else:
                return_set.append((a, b))
    return return_set


def set_to_power(finite_set, power):
    if power == 1:
        return finite_set
    else:
        return multiply(finite_set, set_to_power(finite_set, power - 1))

a = [0, 1]
print(set_to_power(a, 2))

下面的输出是:[(0, 0), (0, 1), (1, 0), (1, 1)]
我正在尝试计算 set_to_power 的时间复杂度,但我不确定它是否为 n2

先来看看multiply: 您有 2 个没有中断或 returns 的嵌套 for 循环,因此无论其中包含什么,都将执行 len(A) * len(B) 次。 add_to_tuple 取决于元组的大小;我稍后再讲,现在让我们假设 power = 2 的基本情况,所以 add_to_tuple 不会发生。在那种情况下,它们是常数,所以除了 for 循环之外的所有东西都在 O(1) 时间内。所以是的,你的乘法将 运行 在 O(n^2) 时间内获得幂 2.

当我们到达 set_to_power 函数时,问题就开始了 - 它有 2 个参数,这两个参数都会影响函数的复杂性。所以它肯定不会是 O(n^2) - 我们必须以某种方式考虑功率变量。 首先,你的 add_to_tuple 是 O(i) (90% 确定,虽然我不确定 list() 和 tuple()) 的内部工作原理幂变量项。所以这已经意味着 multiply 中的每个 运行 将占用 O(i*(n^2))。 然后你触发了 i 次,每次在其中一个集合中有 n 倍的元素。 我 thiiiiink 它会像 ~ O(i^2 * n * n^i) (i 大致是 power-1,但我不确定它与 Big-O 符号有什么关系。)

让我们一一看看所有的功能-

1.add_to_tuple(tup, a)

这个函数是 O(n) 其中 n 是元组的长度。在函数中,tup = list(tup)O(n) 与 final tuple(tup) 相同,其中 tup 是列表。在 python 中,tuple <--> list 之间的转换是 O(n),因为 python 需要复制 list/[ 中的所有项目=20=] 给另一个。因此,在列表中添加成员时使用 list,最后您可以根据需要将 list 转换为 tuple

2.multiply(A, B)

这是 O(len(A) * len(B) * O(add_to_tuple),因为我们正在遍历 AB 中的所有项目,然后为每个项目调用 add_to_tuple = O(n^3) 其中 n 是集合的长度 (A)

3.set_to_power(finite_set, power)

假设 powerkfinite_set 的初始长度是 n。我们将调用 multiply 函数 k 次,在每次调用 multiply 函数期间,结果 finite_set 将成为先前大小的 n 倍. 例如[0, 1][0, 1, 2]finite_set

>>> set_to_power([0, 1], 1)
[0, 1]
>>> set_to_power([0, 1], 2)
[(0, 0), (0, 1), (1, 0), (1, 1)]
>>> set_to_power([0, 1], 3)
[(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 0), (0, 0, 1), (0, 1, 1), (1, 0, 1), (1, 1, 1)]
>>> set_to_power([0, 1, 2], 1)
[0, 1, 2]
>>> set_to_power([0, 1, 2], 2)
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
>>> set_to_power([0, 1, 2], 3)
[(0, 0, 0), (0, 1, 0), (0, 2, 0), (1, 0, 0), (1, 1, 0), (1, 2, 0), (2, 0, 0), (2, 1, 0), (2, 2, 0), (0, 0, 1), (0, 1, 1), (0, 2, 1), (1, 0, 1), (1, 1, 1), (1, 2, 1), (2, 0, 1), (2, 1, 1), (2, 2, 1), (0, 0, 2), (0, 1, 2), (0, 2, 2), (1, 0, 2), (1, 1, 2), (1, 2, 2), (2, 0, 2), (2, 1, 2), (2, 2, 2)]

所以总复杂度将为 -

First call to multiply - O(n^3) +
Second call to multiply, the new finite_set will be of length n*n - O((n*n)^3) + 
Third call to multiply - O((n*n*n)^3) + ... k times

您的解决方案 O(N^3k)


您可以将复杂性降低到 O(N^2k),方法是修复 add_to_tuple 中的转换并使用 lists 来代替 append 的分摊 O(1) 复杂性] 操作。

附带说明一下,该问题的最佳解决方案的复杂度为 O(k*N^k),因为结果集中将有 N^K 个项目,每个项目的长度为 k。您生成的基本上是所有长度为 k 的项目,它们是给定初始集合的排列,其中每个元素都可以重复(例如,请参见上面 set_to_power([0, 1, 2], 3) 的输出)。我们可以从一个元素 [0, 0, 0] 开始,然后将其添加到我们的答案中,然后在每一步中不断增加需要增加的最后一位数字

e.g. [0, 0, 0] -> [0, 0, 1] -> [0, 0, 2] -> [0, 1, 0] -> [0, 1, 1] ...

每一步都可以是 O(k),因为您需要创建先前列表的副本,然后在 O(1) 时间内将其修改为下一个数字。这将导致 O(k*N^k) 解决方案。