简体   繁体   中英

What is the space complexity of enumerating subsets?

This is based off other question I had on space complexity. Permutation Space Complexity

This is a solution to my problem of enumerating(naming) all sets. (Tested it, it works)

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);           
    }
}

I know that the time complexity of my algorithm of is O(2 n ) because a solution in discrete math is that a set n has 2 n subsets. Is this an acceptable way of evaluating the time complexity of this algorithm(couldn't find a recurrence relation to do this)?

Moving on though, I am still having difficulties of evaluating the space complexity. I am trying to apply what I learned from my last question. In my last question which was on permutations of a String, @ajb said that because of the fact that I store a local string that grows by one on each recursive call, my space complexity is actually O(n 2 ) .

I am trying to apply that same here. Lets say my test set is {1,2,3}. To generate the subset {1,2,3}, from my algorithm, when {1,2,3} is finally printed, these "subsets" also exist in memory, - {1}, {}, {1,2},{1],{1,2,3}, {1,2}, meaning its not just a subset that has one less element like in the permutations problem. I also made copies of the leftovers at each round so that one operation in one side won't affect the copy on the other side. This is why I wasn't sure if @ajb's strategy would work here. Would the runtime still be O(n 2 ) or would it be something greater?

Typically the way you'd go about analyzing complexity if you want a good bound is through a mix of amortized analysis and other methods - for example, you can try to rewrite the recursion in an iterative form for easier analysis.

To answer your questions more directly: Your runtime is not O(2^n).

These parts of your code is going to increase the complexity to O(n*2^n)

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

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

The reason being that you are going to iterate over not just every subset, but every element of every subset.

With regards to your space complexity question, and assuming garbage collection is on top of things, then yes the space complexity is O(n^2). Even though you are making copies of 2 things instead of just 1, the complexity is still O(n^2) because that only affects the constant factor. If you actually want to save a list of all the subsets, that puts the space complexity all the way up to O(n*2^n) - which you'll still need for your output currently anyway.

You can solve this with a bivariate recurrence relation. The time complexity of a method call depends on the size of s and the size of the hash set (let's call it h ). Then:

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)

And we are interested in T(n, 0) . The same recurrence relation holds for the space complexity, except that S(0, h) = 0 . This assumes that the created sets are never destroyed.

Each instance of T(s, ...) generates two instances of T(s - 1, ...) . We start with T(n, ...) and end up with 2^n instances of T(0, ...)=O(1) . So

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

Additionally, we must account for the generated h s and s-1 s. Let's start with the s-1 s. After the first expansion, there will be 1 n-1 . After the second expansions there will be additional two n-2 . After the third expansion additional 4 n-3 ... And n expansions are performed that generate those terms. In total: Sum {i from 1 to n} (2^(i-1)*(ni)) = 2^n - n - 1 . So:

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

Lastly, the h terms. This is a bit tricky to figure out. The first expansion will just result in a zero term. The next expansion results in one 1 and one 0 term. Then one 2 , two 1 , one 0 . The number is always equal to the binomial coefficient: Sum { e from 0 to n-1 } (Sum { i from 0 to e } ( i * Binom(e, i) )) = 1 - (n-1)*2^n . So finally:

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

The same complexity applies to the space requirements if all created sets are kept.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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