枚举子集的空间复杂度是多少?

时间:2015-03-28 20:58:37

标签: java algorithm recursion time-complexity space-complexity

这是基于我对空间复杂性的其他问题。 Permutation Space Complexity

这是我对枚举(命名)所有集合的问题的解决方案。 (经测试,它有效)

public static void subsets(Set<Integer> s) {
    Queue<Integer> copyToProtectData  = new LinkedList<Integer>();
    for(int member: s) {
        copyToProtectData.add(member);
    }
    generateSubsets(copyToProtectData, new HashSet<Integer>());
}
private static void generateSubsets(Queue<Integer> s, 
         Set<Integer> hashSet) {
    if(s.isEmpty()) {
        System.out.println(hashSet);
    } else {
        int member = s.remove();
        Set<Integer> copy = new HashSet<Integer>();
        for(int i:hashSet) {
            copy.add(i);
        }
        hashSet.add(member);
        Queue<Integer> queueCopy = new LinkedList<Integer>();
        for(int i:s){
            queueCopy.add(i);
        }
        generateSubsets(s, hashSet);
        generateSubsets(queueCopy, copy);           
    }
}

我知道我的算法的时间复杂度是 O(2 n ,因为离散数学中的解决方案是集合n有2 n 子集。这是评估此算法时间复杂度的可接受方式(无法找到这样做的递归关系)?

继续前进,我仍然难以评估空间复杂性。我正在尝试应用我从上一个问题中学到的东西。在我关于字符串排列的最后一个问题中,@ ajb说由于我在每次递归调用时存储了一个增长1的本地字符串这一事实,我的空间复杂度实际上是 O(n 2功能

我想在这里申请同样的东西。可以说我的测试集是{1,2,3}。要从我的算法生成子集{1,2,3},最后打印{1,2,3}时,这些“子集”也存在于内存中, - {1},{},{1,2} ,{1],{1,2,3},{1,2},意味着它不仅仅是在排列问题中具有少一个元素的子集。我还在每一轮制作剩菜的副本,这样一方的一个操作不会影响另一方的副本。这就是为什么我不确定@ ajb的策略是否适用于此。运行时是否仍然是 O(n 2 还是会更大?

2 个答案:

答案 0 :(得分:1)

通常情况下,如果想要一个良好的界限,你可以通过混合amortized analysis和其他方法来分析复杂性 - 例如,你可以尝试以迭代形式重写递归以便于分析

更直接地回答您的问题:您的运行时间不是O(2 ^ n)。

代码的这些部分会将复杂性增加到O(n * 2 ^ n)

    for(int i:hashSet) {
        copy.add(i);
    }

    for(int i:s){
        queueCopy.add(i);
    }

原因在于您不仅要迭代每个子集,而且要遍历每个子集的每个元素。

关于你的空间复杂性问题,并假设垃圾收集是最重要的,那么空间复杂度是O(n ^ 2)。即使您正在复制2个而不是1个,但复杂性仍为O(n ^ 2),因为这只会影响常数因子。如果你真的想要保存所有子集的列表,那么空间复杂度一直到O(n * 2 ^ n) - 你目前仍然需要输出。

答案 1 :(得分:1)

您可以使用双变量递归关系来解决此问题。方法调用的时间复杂度取决于s的大小和哈希集的大小(让我们称之为h)。然后:

T(s, h) = O( h               //First loop
             + s - 1)         //Second loop 
           + T(s - 1, h + 1) //First recursive call
           + T(s - 1, h)     //Second recursive call
T(0, h) = O(1)

我们对T(n, 0)感兴趣。除了S(0, h) = 0之外,相同的递归关系适用于空间复杂度。这假定创建的集永远不会被破坏。

T(s, ...)的每个实例都会生成T(s - 1, ...)的两个实例。我们从T(n, ...)开始,最后得到2^n T(0, ...)=O(1)个实例。所以

T(n, 0) = 2^n * O(1) + ...

此外,我们必须考虑生成的hs-1。让我们从s-1开始。第一次扩展后,将有1 n-1。在第二次扩展之后,还会有两个n-2。在第三次扩展之后,额外的4 n-3 ...和n扩展将执行生成这些术语。总计:Sum {i from 1 to n} (2^(i-1)*(n-i)) = 2^n - n - 1。所以:

T(n, 0) = 2^n * O(1) + O(2^n - n - 1) + ...

最后,h条款。弄清楚这有点棘手。第一次扩张只会导致零期。下一次展开会产生一个1和一个0字词。然后是一个2,两个1,一个0。该数字始终等于二项式系数:Sum { e from 0 to n-1 } (Sum { i from 0 to e } ( i * Binom(e, i) )) = 1 - (n-1)*2^n。最后:

T(n, 0) = 2^n * O(1) + O(2^n - n - 1) + O(1 - (n-1)*2^n)
        = O(n*2^n)

如果保留所有创建的集合,则相同的复杂性适用于空间要求。