这个函数(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:按值传递 hashtable
和 list
// 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 <= 10
),在循环结束时,您将在@ 中保留N
元素987654329@。此外,hashtable
中的 N
键中的每个 String
最多包含 S
字符。最后,hashtable
中的 N
值中的每个 Integer
都是常量。
同样,list
中可能有多个 M
字符串,其中每个 String
最多可包含 S
个字符。
最后,Integer a
对分析没有帮助,因为它引用了已经占用的内存。我们仍然可以考虑这个Integer a
常量内存。
因此,假设在方法中声明了hashtable
和list
,那么您将看到O(N*S + M*S + I)
的空间复杂度。
也就是说,渐近地说,我们并不真正关心I
(Integer a
),因为它的大小可能比N
和M
小得多。同样,S
可能比N
和M
都小得多。这意味着空间复杂度为O(N + M)
。因为两者都是线性项,我们可以(小心地)将其简化为O(n)
,其中n
是一个线性项,它是N and M
的线性组合。
示例 2:通过引用传递 hashtable
和 list
或在其他地方声明(如您的示例)
// 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);
在这个方法中,list
和hashtable
已经分配到别处了,也就是说这个方法的空间复杂度是O(1)
,因为我们只在Integer a
和String 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),无论它是否位于循环中。
不过,一般来说,如果您指的是在每次迭代中为list
和hashtable
重复使用内存中的同一位置,那么答案会更简单。考虑以下几点:
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;博士
但是,在您的具体情况下,list
和 hashtable
都已在程序的其他地方分配,因此此处未介绍,因此它们不会增加复杂性,Integer a
和 String 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 i
和 Integer a
之外,此方法不分配任何 额外 空间。这意味着这个循环显然具有恒定的空间复杂度。即。 O(1)。
为了进一步澄清,我想问你一个问题:
你把(迭代)二分搜索称为 O(n) 空间复杂度算法吗?
绝对不是。
您的函数 check_10() 使用预先分配的列表和哈希表(就像迭代二分搜索使用预先分配的排序数组一样)和 2 个常量空间变量,因此它的空间复杂度为 O(1)。
PS:我从这里开始澄清 OP 在这个答案的 cmets 中提出的疑问 ->
正如 MichaelRecachinas 所指出的,此循环中的 String
和 Integer
是引用。它们不是副本,因此不会对该函数的空间复杂度产生任何影响。
PPS:Integer a
和 String i
仅分配一次内存,然后在循环的每次迭代中重复使用。
【讨论】:
@FrostCantelope 是的,i
和 a
在整个循环中重复使用。每次完成 1 次迭代时,i
和 a
在下一次迭代中被初始化为它们的新值。
需要进一步澄清的是,在这个for-each循环中,String i
实际上并没有分配内存。它只是一个引用list
中的元素的迭代器,因此对分析没有贡献(因为它引用了先前分配的内存)。类似地,hashtable.get
返回一个引用,因此Integer a
不是分配的内存,而是对先前分配的内存(函数外部)的引用。这不会改变函数是 O(1) 的事实。
然而,澄清是相当迂腐的,通常你仍然会认为String i
和Integer 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)?的主要内容,如果未能解决你的问题,请参考以下文章