Что бы Big O был вложенным циклом с Any() внутри него?
Эти вопросы в основном следуют из моего ответа здесь. Я действительно хотел сказать, что такое Big-O этого алгоритма, но я не был уверен, что моя претензия была полностью звуковой.
Итак, заданы два массива:
B = [ "Hello World!", "Hello Stack Overflow!", "Foo Bar!", "Food is nice...", "Hej" ]
A = [ "World", "Foo" ]
что такое Big O of:
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()
соответствует...
Ответы
Ответ 1
Пусть длина A
be N
и длина B
be 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)]
Ответ 2
Он закрывается, однако, как говорят другие, это будет O(n*m)
, поскольку размеры каждой из ваших коллекций различаются (при лучшем случае O(n)
и худшем O(n*m)
), в противном случае, если бы вы повторяли то же самое дважды, вы получите O(n^2)
.
Взгляд за сценами в Any()
Вы можете взглянуть на источник метода Enumerable.Any()
, чтобы вникнуть в это немного дальше. Вы увидите цикл 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)
.
Ответ 3
.Any()
должен быть O(n)
, когда он ищет контейнер, пока не найдет первый соответствующий экземпляр. Таким образом, в цикле foreach должен быть O(n^2)
.
Ответ 4
Я предполагаю, что вы предоставляете A
и B
только, например, их содержимое, и вы знаете, что сложность имеет смысл только для наборов входов (например, средних, худших и лучших случаев), а не для отдельных входов.
Моя точка зрения, в зависимости от требований проблемы и использования, вы можете сделать очень разные оценки сложности кода.
Пусть n
be A.Length
и m
be 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)
.
Ответ 5
он по существу является вложенным циклом, поэтому большой O должен быть O (n ^ 2) для наихудшего сценария