繁体   English   中英

试图理解这个动态递归子集和的时间复杂度

[英]Trying to understand the time complexity of this dynamic recursive subset sum

# Returns true if there exists a subsequence of `A[0…n]` with the given sum
def subsetSum(A, n, k, lookup):
 
    # return true if the sum becomes 0 (subset found)
    if k == 0:
        return True
 
    # base case: no items left, or sum becomes negative
    if n < 0 or k < 0:
        return False
 
    # construct a unique key from dynamic elements of the input
    key = (n, k)
 
    # if the subproblem is seen for the first time, solve it and
    # store its result in a dictionary
    if key not in lookup:
 
        # Case 1. Include the current item `A[n]` in the subset and recur
        # for the remaining items `n-1` with the decreased total `k-A[n]`
        include = subsetSum(A, n - 1, k - A[n], lookup)
 
        # Case 2. Exclude the current item `A[n]` from the subset and recur for
        # the remaining items `n-1`
        exclude = subsetSum(A, n - 1, k, lookup)
 
        # assign true if we get subset by including or excluding the current item
        lookup[key] = include or exclude
 
    # return solution to the current subproblem
    return lookup[key]
 
 
if __name__ == '__main__':
 
    # Input: a set of items and a sum
    A = [7, 3, 2, 5, 8]
    k = 14
 
    # create a dictionary to store solutions to subproblems
    lookup = {}
 
    if subsetSum(A, len(A) - 1, k, lookup):
        print('Subsequence with the given sum exists')
    else:
        print('Subsequence with the given sum does not exist')

据说这个算法的复杂度是O(n * sum),但是我不明白如何或为什么; 有人能帮我吗? 可能是冗长的解释或递归关系,什么都可以

我能给出的最简单的解释是意识到当lookup[(n, k)]有一个值时,它是 True 或 False 并指示A[:n+1]的某个子集是否与k相加。

想象一个简单的算法,它只是逐行填充查找的所有元素。

lookup[(0, i)] (对于 0 ≤ itotal )只有两个元素为真, i = A[0]i = 0 ,所有其他元素都是假的。

如果lookup[(0, i)]为真或i ≥ A[1]lookup[(0, i - A[1])为真,则lookup lookup[(1, i)] (对于0 ≤ itotal )为真真的。 我可以通过使用A[i]或不使用来达到总和i ,而且我已经计算了这两个。

... 如果lookup[(r, i)] lookup[(r - 1, i)]为真或i total i ≥ A[r]并且lookup[(r - 1, i - A[r])为真。

以这种方式填充这个表,很明显我们可以在len(A) * total的时间内完全填充行0 ≤ row < len(A)的查找表,因为线性填充每个元素。 我们的最终答案只是检查表中是否(len(A) - 1, sum) True。

您的程序正在做完全相同的事情,但会根据需要计算lookup条目的值。

抱歉提交了两个答案。 我想我想出了一个稍微简单的解释。

想象一下您的代码将三行放入if key not in lookup:到单独的 function, calculateLookup(A, n, k, lookup)中。 我将调用“为 n 和 k 调用nk的特定值nk的成本是调用calculateLookup calculateLookup(A, n, k, loopup)所花费的总时间,但不包括任何递归调用calculateLookup

关键的见解是,如上所述,对任何nk调用calculateLookup()的成本是 O(1)。 由于我们在成本中排除了递归调用,并且没有 for 循环,因此calculateLookup的成本是仅执行几个测试的成本。

整个算法做固定量的工作,调用calculateLookup ,然后做少量的工作。 因此,在我们的代码中花费的时间与询问我们调用了多少次calculateLookup相同?

现在我们回到之前的答案。 由于查找表,每次调用calculateLookup时都会使用不同的(n, k)值。 我们还知道,我们在每次调用calculateLookup之前检查nk的边界,因此1 ≤ k ≤ sum0 ≤ n ≤ len(A) 因此, calculateLookup最多被调用(len(A) * sum)次。


一般来说,对于这些使用 memoization/cacheing 的算法,最简单的做法是分别计算然后求和:

  1. 假设您需要的所有值都被缓存,事情需要多长时间。
  2. 填充缓存需要多长时间。

您提出的算法只是填满了lookup缓存。 它以不寻常的顺序执行它,并且它没有填充表中的每个条目,但这就是它所做的全部。


代码会稍微快一点

lookup[key] =  subsetSum(A, n - 1, k - A[n], lookup) or subsetSum(A, n - 1, k, lookup)

在最坏的情况下不会改变代码的 O(),但可以避免一些不必要的计算。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM