这个函数(for循环)空间复杂度是O(1)还是O(n)?

Posted

技术标签:

【中文标题】这个函数(for循环)空间复杂度是O(1)还是O(n)?【英文标题】:Is this function (for loop) space complexity O(1) or O(n)? 【发布时间】:2016-04-01 22:27:48 【问题描述】:
public void check_10() 
    for (string i : list) 
        Integer a = hashtable.get(i);
        if (a > 10) 
            hashtable.remove(i);
        
    

这是 O(1) 还是 O(n)?我猜是 O(n),但它不是每次都重用内存点 a 使其 O(1) 吗?

【问题讨论】:

没错,但空间复杂度不是基于最大的内存消耗吗?如果有 else return true 会是 O(n) 吗? 我继续并更改为 hashtable.remove(i) 而不是 return true - 这就是我没有别的原因 @azurefrog:为什么a > 10 很重要? @RohitRawat 因为我看不懂:p 【参考方案1】:

这具有 O(n) 空间复杂度,因为列表占用了该空间。 :)。哈希表最多是另一个 O(n),所以空间复杂度的总和仍然是 O(n)。

【讨论】:

你是否将(迭代)二分搜索称为 O(n) 空间复杂度算法只是因为它使用数组? @RohitRawat 请注意这个语句——它确实取决于int binary_search(T arr[], T val) 中的数组是通过值传递还是通过引用传递。如果我们假设它是按值传递的,因此制作了数组的副本,那么它在空间中确实是 O(n)。如果它是通过引用传递的,那么正如您所暗示的,它是 O(1)。 (此处提供的示例:cs.northwestern.edu/academics/courses/311/html/…)【参考方案2】:

空间复杂度询问“我在这段代码的 sn-p 中使用了多少额外空间(渐近地说)”。以下是空间复杂度分析的工作原理,显示了两种一般情况(对于您的代码 sn-p):

示例 1:按值传递 hashtablelist

// assume `list` and `hashtable` are passed by value
public void check_10(List<String> list, HashMap<String, Integer> hashtable) 
    for (String i : list) 
        Integer a = hashtable.get(i);
        if (a > 10) 
            hashtable.remove(i);
        
    

假设您在hashtable 中有N 元素并且没有删除任何元素(即,对于所有N 元素,a &lt;= 10),在循环结束时,您将在@ 中保留N 元素987654329@。此外,hashtable 中的 N 键中的每个 String 最多包含 S 字符。最后,hashtable 中的 N 值中的每个 Integer 都是常量。

同样,list 中可能有多个 M 字符串,其中每个 String 最多可包含 S 个字符。

最后,Integer a 对分析没有帮助,因为它引用了已经占用的内存。我们仍然可以考虑这个Integer a 常量内存。

因此,假设在方法中声明了hashtablelist,那么您将看到O(N*S + M*S + I) 的空间复杂度。

也就是说,渐近地说,我们并不真正关心IInteger a),因为它的大小可能比NM 小得多。同样,S 可能比NM 都小得多。这意味着空间复杂度为O(N + M)。因为两者都是线性项,我们可以(小心地)将其简化为O(n),其中n 是一个线性项,它是N and M 的线性组合。

示例 2:通过引用传递 hashtablelist 在其他地方声明(如您的示例)

// assume `list` and `hashtable` are passed by reference or
// declared elsewhere in the class as in
//
// public void check_10() 
public void check_10(List<String> list, HashMap<String, Integer> hashtable) 
    for (String i : list) 
        Integer a = hashtable.get(i);
        if (a > 10) 
            hashtable.remove(i);
        
    

在这个方法中,listhashtable已经分配到别处了,也就是说这个方法的空间复杂度是O(1),因为我们只在Integer aString i中使用了常量空间(甚至尽管从技术上讲,它们是对先前分配的内存的引用——您可以将常量空间视为存储引用的结果。

但它不是每次都重用内存点 a 使其 O(1) 吗?

这取决于您所说的“重用”内存中的位置是什么意思。理论上,空间复杂度分析并没有完全考虑这个意义上的语言的实现细节。这意味着如果你有一个像

这样的循环
for (int i = 0; i < N; i++) 
    T myvar = new T();

您没有考虑每次循环迭代后myvar 发生的事情的影响。通过“正在发生的事情的含义”我的意思是,垃圾收集器是在每次迭代后回收内存,还是你不断地在堆上分配 N 个内存点?在 GC 情况下,它将是 O(1),因为您正在重用内存。在“无限”分配情况下,它将是O(N),因为您现在分配了N 点。同样,理论上,这通常不会在分析中考虑,任何T myvar = new T() 通常被认为是 O(1),无论它是否位于循环中。

不过,一般来说,如果您指的是在每次迭代中为listhashtable 重复使用内存中的同一位置,那么答案会更简单。考虑以下几点:

public void foo() 
    int list[] = 1, 2, 3, 4;
    for (int i = 0; i < list.length; i++) 
        System.out.println(list[i]);
    

即使list 被声明了一次并且我们只是迭代list 并打印内容,foo() 的内存复杂度仍然是 O(n),因为我们已经分配了 list,其中在渐近case 最多可以有n 个元素。因此,无论它是否重用内存中相同或不同的点,它们都仍然会导致线性空间复杂度。

tl;博士

但是,在您的具体情况下,listhashtable 都已在程序的其他地方分配,因此此处未介绍,因此它们不会增加复杂性,Integer aString i 是只存在于记忆中。因此,这个方法将是O(1)

【讨论】:

谢谢!是否有必要表示 n = N+M 或者只声明 O(n) 空间复杂度(减少时)是否可以?它与 O(2n) -> O(n) 的概念几乎相同吗? 可以肯定地说复杂度是线性的,所以只要你认识到线性复杂度的贡献者,O(n) 就足够了。是的,它与 O(2n) -> O(n) 的概念大致相同。 谢谢!最后一件事... Vector remove(item):我知道时间复杂度是 O(n),因为需要完成转移(除非最后)。移位的空间复杂度是多少? @MichaelRecachinas 不,它具有 O(1) 空间复杂度。哈希表和列表已经分配,​​不计算在内。 @RohitRawat 很好。在我写答案的时候,我以为他们已经在方法中声明了。已编辑。【参考方案3】:

是 O(1)

除了 2 个常量大小的变量 string iInteger a 之外,此方法不分配任何 额外 空间。这意味着这个循环显然具有恒定的空间复杂度。即。 O(1)

为了进一步澄清,我想问你一个问题:

你把(迭代)二分搜索称为 O(n) 空间复杂度算法吗?

绝对不是。

您的函数 check_10() 使用预先分配的列表和哈希表(就像迭代二分搜索使用预先分配的排序数组一样)和 2 个常量空间变量,因此它的空间复杂度为 O(1)。

PS:我从这里开始澄清 OP 在这个答案的 cmets 中提出的疑问 ->

正如 MichaelRecachinas 所指出的,此循环中的 StringInteger 是引用。它们不是副本,因此不会对该函数的空间复杂度产生任何影响。

PPSInteger aString i 仅分配一次内存,然后在循环的每次迭代中重复使用。

【讨论】:

@FrostCantelope 是的,ia 在整个循环中重复使用。每次完成 1 次迭代时,ia 在下一次迭代中被初始化为它们的新值。 需要进一步澄清的是,在这个for-each循环中,String i实际上并没有分配内存。它只是一个引用list 中的元素的迭代器,因此对分析没有贡献(因为它引用了先前分配的内存)。类似地,hashtable.get 返回一个引用,因此Integer a 不是分配的内存,而是对先前分配的内存(函数外部)的引用。这不会改变函数是 O(1) 的事实。 然而,澄清是相当迂腐的,通常你仍然会认为String iInteger a 具有恒定的内存,因此 O(1)。 @xordux HashMap.get 返回对 HashMap 中对象的引用,因为在 Java 中,对象是通过引用传递的。 (完全迂腐:Java 按值传递对象引用,因此您正在HashMap.get 获取到对象的内存地址副本。)(参见***.com/questions/764837/… 和javaworld.com/article/2077424/learn-java/…)。您可以按照您所说的在循环中更改它们的值,但这并不意味着它们不是对对象的引用 @xordux 它们不是不允许重新分配的 C 样式引用。 Java 中的“引用”更类似于 C 中的指针。关于 String i幕后 Java foreach 实际上等同于使用迭代器(参见***.com/questions/28351773/…)。这并不意味着我们看到了迭代器。相反,我们在list 中看到了对内存的“引用”。请注意,我只是想建议它不会复制list 中的每个元素并将其放置在i(或类似的a)中。

以上是关于这个函数(for循环)空间复杂度是O(1)还是O(n)?的主要内容,如果未能解决你的问题,请参考以下文章

在循环中声明的变量是不是会使空间复杂度 O(N)?

如何计算嵌套在 for 循环中的 while 循环的时间复杂度?

嵌套循环的大O时间复杂度

这个程序的空间复杂性是多少?

算法 基础

Python中嵌套For循环的时间复杂度