这个问题基本上是我answer here的后续问题。我真的想说这个算法的Big-O是什么,但我不确定我的说法是完全合理的。
给定两个数组:
B = [ "Hello World!", "Hello Stack Overflow!", "Foo Bar!", "Food is nice...", "Hej" ]
A = [ "World", "Foo" ]
什么是大O:
List<string> results = new List<string>();
foreach (string test in B)
{
if (A.Any(a => test.Contains(a))
results.Add(test);
}
我认为它介于O(n)
和O(n^2)
之间,因为它取决于Any()
匹配的结果...
答案 0 :(得分:22)
A
的长度为N
,B
的长度为M
。我们有两个极端情况:
最差:
a => test.Contains(a)
在每个false
上返回a
,因此A.Any
必须扫描整个 A
,我们有
O(N * M)
最佳一个:
a => test.Contains(a)
在true
的第一项上返回A
,因此A.Any
立即返回,我们只有
O(M)
实际复杂性介于两者之间(包括两个边界):
[O(M)..O(M*N)]
答案 1 :(得分:10)
它很接近,但正如其他人提到的那样,O(n*m)
因为每个集合的大小不同(最佳情况为O(n)
,最差情况为O(n*m)
)否则,如果你两次迭代相同大小的集合,你会得到O(n^2)
。
在Any()
您可以查看the source for the Enumerable.Any()
method以进一步深入了解这一点。你会看到一个foreach
循环来迭代,直到它找到一个匹配谓词,这加强了你对O(n)
的假设:
public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
// Another loop to iterate through the collection until the predicate is matched
foreach (TSource element in source) {
if (predicate(element)) return true;
}
return false;
}
正如您所看到的,Any()
本身将是O(n)
,因为它的长度和嵌套在现有循环O(m)
内应该为您提供O(n*m)
码。但是,它可能会低至O(m)
。
答案 2 :(得分:5)
.Any()
应该是O(n)
,因为它会搜索容器,直到找到第一个匹配的实例。因此,在foreach循环中应该是O(n^2)
。
答案 3 :(得分:5)
我假设您仅提供A
和B
作为其内容的示例,并且您知道复杂性仅对输入集合(如平均,最差和最佳情况)有意义,而不是个人投入。
我的观点是,根据问题要求和用例,您可以对代码复杂性做出非常不同的估计。
让n
为A.Length
,m
为B.Length
。然后可以用几种不同的方式计算给定代码的复杂性:
假设string.Contains
为O(1)
。在实践中,可以做出如此强烈的假设,例如,如果我们确切地知道没有字符串比预先确定的长度更长。然后代码复杂性当然是 O(n*m)
。
假设string.Contains
为O(x*y)
,其中x
和y
是干草堆和针的长度。设A
中最长的字符串长度为k_A
,B
中最长的字符串长度为k_B
。然后代码复杂性为 O(n*m*k_A*k_B)
对于第二种情况,还有另一种(更实际的)方法:
让S_A
为A
中所有字符串的长度总和,S_B
为B
中所有字符串的长度总和。然后代码复杂性为 O(S_A * S_B)
。
这是最糟糕的情况。但就平均情况而言,对于大多数实际案例,string.Contains
为O(x + y)
。因此,平均代码复杂度为 O(n*m*(k_A + k_B))
或 O((S_B + k_A) * n + (S_A + k_B) * m)
。
答案 4 :(得分:0)
它本质上是一个嵌套的for循环,所以对于最坏的情况,大O应该是O(n ^ 2)