Java 这个子集算法的空间复杂度实际上是O(n)?

Java 这个子集算法的空间复杂度实际上是O(n)?,java,algorithm,recursion,time-complexity,space-complexity,Java,Algorithm,Recursion,Time Complexity,Space Complexity,这是破解编码的问题9.4 问题:编写一个方法返回集合的所有子集 这是我的Java解决方案。(经过测试,效果很好!!!) 公共静态列表子集(集合s){ 队列copyToProtectData=newlinkedlist(); for(国际成员:s){ copyToProtectData.add(成员); } 列表子集=新的ArrayList(); 生成子集(copyToProtectData、子集、新HashSet()); 返回子集; } 专用静态void生成子集(队列, 列表子集,集合hashS

这是破解编码的问题9.4
问题:编写一个方法返回集合的所有子集

这是我的Java解决方案。(经过测试,效果很好!!!)

公共静态列表子集(集合s){
队列copyToProtectData=newlinkedlist();
for(国际成员:s){
copyToProtectData.add(成员);
}
列表子集=新的ArrayList();
生成子集(copyToProtectData、子集、新HashSet());
返回子集;
}
专用静态void生成子集(队列,
列表子集,集合hashSet){
如果(s.isEmpty()){
添加子集(哈希集);
}否则{
int member=s.remove();
Set copy=new HashSet();
for(int i:hashSet){
副本.添加(i);
}
hashSet.add(成员);
Queue queueCopy=new LinkedList();
用于(int i:s){
添加(i);
}
生成子集(s、子集、哈希集);
生成子集(队列副本、子集、副本);
}
}
我研究了这个问题的解决方案,作者说这个算法的解决方案在O(2n)时间复杂度和O(2n)空间复杂度下运行。我同意她说,这个算法运行在O(2n)的时间,因为要解决这个问题,你必须考虑的事实是,对于任何元素,你有两种可能性,它可以在集合中或者不在集合中。因为你有n个元素,你的问题有2n个可能性,所以这个问题可以用O(2n)时间来解决

然而,我相信我有一个令人信服的论点,我的算法运行在O(n)空间。我知道空间复杂度是“一个算法相对于输入大小占用的总空间” 与递归调用的深度有关(记得我在Youtube上看过的视频)

我的一个例子是生成[1,2,3]作为[1,2,3]的子集。下面是生成该集合的递归调用集
生成子集([],子集,[1,2,3])
生成子集([3],子集,[1,2])
生成子集([2,3],子集,[1])
生成子集([1,2,3],子集,[])


这表明递归调用相对于原始集合大小n的最大深度是n本身。每个递归调用都有自己的堆栈框架。从这里,我得出结论,空间复杂度是O(n),有人在我的证明中看到任何缺陷吗

您需要考虑算法分配的所有内存(或者,更确切地说,任何时候“正在使用”的最大分配内存量)-不仅在堆栈上,而且在堆上。生成的每个子集都存储在
子集列表中,该列表最终将包含2n个集合,每个集合的大小介于0和n之间(大多数集合包含大约n/2个元素),因此空间复杂度实际上是O(n2n)。

您需要考虑算法分配的所有内存(或者,更确切地说,是任何时候“正在使用”的最大分配内存量)-不仅在堆栈上,而且在堆上。生成的每个子集都存储在
子集
列表中,该列表最终将包含2n个集合,每个集合的大小介于0和n之间(大多数集合包含大约n/2个元素)-所以空间复杂度实际上是O(n2n)。

哦,好吧,我同意你的观点,你还必须考虑从堆中分配的内存,这就是她得到O(2^n)的原因.最大集合的大小不是独立于集合的数量而运行的吗?所以你不会将它们相乘吗?@CommittedDroider你肯定可以枚举O(n)中的子集空间,但在这种情况下,算法也决定存储它们,因为有2^n个,这需要很大的空间。包含k个项目的子集的数量等于包含n-k个项目的子集的数量。因此,平均子集大小为n/2,子集的总大小为(n/2)*2^n=O(n*2^n).你们能解释一下,如果列出它们,为什么会是O(n)空间复杂度吗?我从这里了解到,你们还必须考虑从堆中分配的内存,在这种情况下,这些内存是集合的所有大小,n,n-1,n-2。那么会是O(n^2)然后呢?请看我答案中的第一个括号——重要的是在任何给定时间实际使用的最大内存量。假设您分配1 MB,然后再分配1 MB,然后释放前1 MB(或者如果您使用的是垃圾收集语言,如Java,则停止引用它),并再分配1 MB。即使您已分配了3 MB的空间,但使用的空间也不会超过2 MB,因此您的空间消耗量将为2 MB。(请注意,最后一个块可能会分配在与已发布块相同的物理空间中。)哦,好吧,我同意你的观点,你还必须考虑你从堆中分配的内存,这就是她得到O(2^n)的原因。最大集合的大小不是独立于集合的数量而运行的吗?这样你就不会将它们相乘了吗?@committedandroider你肯定可以枚举O(n)中的子集空间,但在这种情况下,算法也决定存储它们,因为有2^n个,这需要很大的空间。包含k个项目的子集的数量等于包含n-k个项目的子集的数量。因此,平均子集大小为n/2,子集的总大小为(n/2)*2^n=O(n*2^n).你们能解释一下,如果列出它们,为什么会是O(n)空间复杂度吗?我从这里了解到,你们还必须考虑从堆中分配的内存,在这种情况下,这些内存是集合的所有大小,n,n-1,n-2。那么会是O(n^2)然后呢?请看我答案中的第一个括号-重要的是在任何给定时间实际使用的最大内存量
public static List<Set<Integer>> subsets(Set<Integer> s) {
    Queue<Integer> copyToProtectData  = new LinkedList<Integer>();
    for(int member: s) {
        copyToProtectData.add(member);
    }
    List<Set<Integer>> subsets = new ArrayList<Set<Integer>>();
    generateSubsets(copyToProtectData, subsets, new HashSet<Integer>());
    return subsets;
}
private static void generateSubsets(Queue<Integer> s, 
        List<Set<Integer>> subsets, Set<Integer> hashSet) {
    if(s.isEmpty()) {
        subsets.add(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, subsets, hashSet);
        generateSubsets(queueCopy, subsets, copy);          
    }
}