我试图用我的算法来计算算法的时间,使用Big O表示法,我无法找到一个非常明确的解释。
基本上,我的算法在于将一个新数组与一个" parent"中的所有其他数组进行比较。阵列。
为此,我有一个for循环,迭代父数组中的所有元素,寻找一个看起来像新创建的数组的数组。
以下是代码:
bool AlreadyExistingArray(Array array)
{
bool areEqual = true;
foreach (Array a in arrayEntries)
{
if (a.count != array.count)
continue;
foreach (int i in array)
{
if (!a.contains(i))
{
areEqual = false;
break;
}
}
if (areEqual)
{
areEqual = false;
foreach (int i in a)
{
if (!a.contains(i))
{
areEqual = false;
break;
}
}
}
}
return areEqual;
}
据我所知,每个for循环应该是O(n),但复杂性是否应该组成?由于我处理不同大小的数组,我很确定复杂性不能被认为是O(n ^ 2)。
希望我明白了!否则,请告诉我,我会进一步澄清。
编辑:更改算法。
答案 0 :(得分:4)
你在这里获得如此多的不同答案的原因是Big-O分析并不像计算程序运行的步数那么简单。向计算机科学家讲授的这个简化版本是Big-O,Big-Omega和Big-Theta渐近边界函数概念的近似(通常是足够的)。实际上有一个formal definition的Big-O可以消除这些含糊之处。
尽管如此,让我们坚持下去。我在评论中的答案是这样的:
调用arrayEntries的大小:
len(arrayEntries) = n
和数组len(array) = m
的大小以及数组len(largestOfarray) = k
中最大条目的大小(即您调用的最大变量的大小<强>一强>)。然后你的算法是O(n(m + k))。无论n,m或k中的哪一个都是永远不会改变大小的常数,只需将它们从该等式中移除即可。
让我解释一下。 Big-O的定义大致是:
在计算机科学中,大O符号用于根据算法如何响应输入大小的变化对算法进行分类,例如算法的处理时间随着问题大小变得非常大而变化。
当你说算法是O(n^2)
时,这意味着什么。你是说你的算法的运行时可以表示为某个函数T(n)
(注意它只有一个变量n)并且渐近T(n)
增长不快于n^2
(也就是说,非常粗略地说,当n变得非常大时,n处的函数f(n) = n^2
的梯度将大于n处的T(n)的梯度。
现在在前面的例子中,算法的运行时间取决于一个变量n。这并非总是如此。想象一下,你有一个这样的程序:
void algorithm(Array arrayM, Array arrayN) {
for(int i : arrayN) {
// runs len(arrayN) = n times
}
for(int j : arrayM) {
// runs len(arrayM) = m times
}
}
此算法的运行时是一个函数,它取决于arrayM和arrayN的大小,因此它将表示为T(n,m)
。现在,如果这些大小是独立变量(即arrayM的大小与arrayN的大小无关),则此算法的运行时为O(m+n)
。但是,如果arrayM的大小确实取决于arrayN的大小(假设它是通过将arrayN的一半元素复制到其中来初始化的),那么len(arrayM) = m
实际上取决于n m = n/2
。因此,您之前O(m+n)
的算法的时间复杂度现在为O(n+n/2) = O(n)
。这本质上是因为您的运行时函数T(n,m)
现在可以写为T(n, n/2) ~ T(n)
,即它是一个变量的函数。
现在,对于您的程序,我们首先假设 arrayEntries len(arrayEntries) = n
的大小和数组 len(array) = m
的大小和数组中最大条目的大小(最大可能 a )len(largestOfarray) = k
是完全独立的变量,彼此不相互依赖。这不是一个不合理的假设,因为你在一篇评论中说过,内部循环并不依赖于外部循环的任何值。因为它是用户输入的数量,并且输入的长度可能是任意长的,因为一个用户可能输入长度为1的东西而另一个用户可能输入1000个字符长的行。
由于n,m和k是独立的,因此您的复杂性就是这样。你的外循环运行n次,在每次迭代中,第一个内循环将运行m次,第二个内循环最差运行k次。因此,您的总复杂度为O(n(m+k))
。但这就是全部吗?好吧不太好。
看到n,m和k有一些界限。即,用户输入(k)的长度可能具有限制(即,如果它是从stdin获取的,那么它可能不会超过1000个字符)。如果是这样,你可以合理地说最坏的k可能是1000,所以我们可以将它视为一个常数,然后你的算法的复杂性是O(nm)
因为常数k被消除了。此步骤完全取决于您对程序的使用方式,但如果您想要安全,则可以说复杂性为O(n(m+k))
。
然而这是有问题的,可能没有人认为m和n也是有限的,因为它们的大小有限(即操作系统将为您的程序分配多少内存)因此被处理作为常数?技术上,是的。在某些算法分析中,这有时是有用的(例如在增长缓慢的算法的情况下)。
最终,这完全取决于您认为您的计划如何运作以及合理的假设(即k可以被视为常数)以及要避免哪些假设。在我个人看来,这个算法可能是O(n(m+k))
,可能是O(nm)
,但我不会削减它比这更多; m和n似乎与你的描述完全独立。
一个有趣的案例研究实际上是@frenzykryger对此答案所评论的内容;我错过了一个细节,因为你在我写答案时编辑了你的问题。评论者说您更改了外循环的开始,以检查 a 的大小是否等于数组的大小。这意味着你的第二个内环运行的次数(即如上所述的k的大小)现在完全取决于m(调用m是数组的大小),即k = m。因此,您的算法为O(n(m+m)) = O(nm)
。现在如果你保证m总是小于n,那么算法将是O(n^2)
(m可以被丢弃)。但是如果m是无界的(可以是任何大小),那么算法仍然是O(nm)
。
正如您所看到的,Big-Oh分析有时候没有一个正确的答案。这一切都取决于你的程序将如何表现,你保证获得什么样的输入,以及许多其他因素。如果所有这些都让你希望有更严格的方式来定义它,那肯定有 - 只是google&#34; Big-Oh正式定义&#34;,阅读一些链接,继续进行数学堆栈交换,你&# 39;让自己有一个保证派对。
答案 1 :(得分:1)
随着应用程序大小的变化,Big O符号与计算增长有关。因此,这里有三个关键问题将决定大O符号,所有这些都与您的应用程序如何缩放有关:
计算上述问题的“是”答案的数量。它们代表您的 n 。三个“是”答案表示 O(n ^ 3)。
关于Big O的更多信息。尽管看起来反直觉,但是将具有500个元素的数组与另一个500个元素的数组进行比较,如果两个数组都没有变化,则时间复杂度为 O(1)。尽管必须进行25,000次比较,但由于比较次数从未改变,因此复杂性降低到1.当您开始想象缓存结果,排序或选择非常有效的数据结构或搜索算法的可能性时,这开始变得有意义。
现在想象一下根据固定大小的文档检查可变大小的文档(例如,拼写检查器会针对牛津英语词典中的所有171,000个单词检查文档)。该文档有 n 字样,但OED中的171,000个单词肯定会对时间复杂度产生一些影响吗?
实际上,没有。复杂性将是 O(n),因为唯一的变量是文档的大小。当您阅读一些数据结构时,这也开始变得有意义,这些数据结构使得对于已知单词列表(例如Trie)快速单词查找非常。检查文档的时间与文档大小呈线性关系。
如果我们将大小 n 的文档与另一个大小为 n的文档进行比较,我们会得到 O(n ^ 2)的复杂性。在这种情况下,当文档大小增加到10,000,000,000时,比较两个大小为1000的文档将不会线性扩展;如果文件的大小是预期会增长的变量,我们可能不得不重新考虑我们的方法。
答案 2 :(得分:1)
最坏情况下的操作次数对算法的时间复杂度很重要,因此循环确实是提示,但两个嵌套循环意味着答案是 O(n²)并不是必需的。算法的最坏情况是,当您的数组等于arrayEntries
中的最后一个数组时,它与arrayEntries
中的所有其他数组几乎相等(除了最后一个元素)。让 N 为arrayEntries
中的数组数量, M 为array
中的元素数量,让array.contains
复杂度为 O(M)。它是 O(M),因为它是数组,因此我假设使用了简单的线性搜索。如果您将使用哈希集替换数组,则array.contains
复杂性将分摊 O(1)。如果你对数组进行预排序并使用二进制搜索,那么复杂性将只是 O(log M) - 如果你将使用一些树集,则相同。您使用的数据结构实际上会影响算法的渐近性。
bool AlreadyExistingArray(Array array)
{
bool areEqual = true;
foreach (Array a in arrayEntries) // Loop executes N times
{
if (a.count != array.count)
continue;
foreach (int i in array) // Loop executes M times
{
if (!a.contains(i)) // has O(M) complexity because a.count == array.count here
{
areEqual = false;
break;
}
}
if (areEqual)
{
areEqual = false;
foreach (int i in a) // executes M times
{
if (!array.contains(i)) // has O(M) complexity
{
areEqual = false;
break;
}
}
}
}
return areEqual;
}
这里你在另一个循环中循环,该循环调用 O(M)的操作。 所以复杂性就像:
N·M·O(M)+ M·O(M)= O(N·M²)。
它将执行第一个嵌套循环 N 次,给出 N·M·O(M),然后在areEqual
变为true时执行第二个循环给出 M·O(M)。我们只对这里的“最高”术语感兴趣,所以我们可以扔掉最后的 M·O(M),这会给出答案 O(N·M²)
如果arrayEntries
的长度是常数,则复杂度为 O(M²)。如果array
的长度不变,则复杂度为 O(N)。
有些算法的 O(n)复杂度由嵌套循环表示,所以不要忘记计算操作次数,而不仅仅是循环。寻找算法的大多数难题是很重要的,因为渐近在“普通”情况和“硬”情况下实际上可能是不同的。通常的例子是快速排序算法 - 它在大多数输入上是渐近的 O(n log n)但是在数组已经排序的情况下它是 O(n²)。即使你的算法在每个输入上都具有相同的渐近 - 在“硬”情况下更容易推理它,因为这是运行时最大化并接近其渐近界限的地方。例如,如果来自arrayEntries
的第一个数组是等于{{1}的数组,那么对于任何 N ,您的算法将使用 O(M²)复杂度。 }。但这并不意味着对于任何 N ,算法的总体复杂度为 O(M²)。
如果您想要熟悉算法,请查阅并阅读Cormen's introduction to algorithms本书。第一章介绍了用于分析不同算法的big-O表示法。
答案 3 :(得分:0)
以更好的方式让我们举一个例子:
你有双重嵌套循环,外部循环依赖于问题大小n,但内部循环依赖于 外循环的索引变量的当前值
for ( int i = 0; i < n; i++ ) {
for ( int j = 0; j < i; j++ ) {
// these statements are executed O(n^2) times
}
}
让我们来分析这种情况下迭代,通过迭代: 在外环(I = 0)的第一迭代中,内环将迭代0次 在外环(I = 1)的第二迭代中,内环将迭代1周时间 在外环(I = 2)的第三迭代中,内环将迭代2次
在外部循环的最后一次迭代(i = N - 1),内循环将 迭代N - 1次
所以,总次数在内部循环的语句将被执行 将等于整数从1到n的总和 - 1,其是:
((n - 1)* n)/ 2 = n ^ 2/2 - n / 2 = O(n ^ 2)次
所以在你的情况下,复杂度是O(n(k + m))= O(n(n + n))= O(n ^ 2)次;
,其中
n -> arrayentries size
k -> array size
m -> a size
这是因为Big-O表示法不是根据您的实际参数找到确切的值。它是关于确定渐近运行时。