繁体   English   中英

算法,大O表示法:这个函数是O(n ^ 2)吗? 还是O(n)?

[英]Algorithm, Big O notation: Is this function O(n^2) ? or O(n)?

这是来自算法书“Java中的数据结构和算法,第6版”的代码。 作者:Michael T. GoodRich,Roberto Tamassia和Michael H. Goldwasser

public static String repeat1(char c, int n)
{
    String answer = "";
    for(int j=0; j < n; j++)
    {
         answer += c;
    }  
    return answer;
}

根据作者的说法,这个算法的Big O表示法是O(n ^ 2),理由是:“命令,答案+ = c,是答案的简写=(答案+ c)。这个命令不会导致新的字符要添加到现有的String实例中;相反,它会生成一个具有所需字符序列的新String,然后重新分配变量answer,以引用该新字符串。就效率而言,这种解释的问题是由于连接而创建一个新字符串,需要的时间与结果字符串的长度成正比。第一次通过此循环时,结果的长度为1,第二次通过循环时结果的长度为2 ,依此类推,直到我们达到长度为n的最终字符串。“

但是,我不明白,这个代码如何具有O(n ^ 2),因为它的原始操作数量只是每次迭代加倍,而不管n的值是什么(不包括j <n和j ++)。 语句答案+ = c每次迭代需要两个基本操作而不管值n,因此我认为这个函数的等式应该是4n + 3.(每个循环操作j

或者,是句子,“就效率而言,这种解释的问题在于,由于连接而创建一个新字符串,需要的时间与结果字符串的长度成正比。”只是简单地说由于串联而创建一个新字符串需要与其长度成比例的时间,而不管函数中使用的基本操作的数量是多少? 因此,原始操作的数量对函数的运行时间没有太大影响,因为连接的String赋值运算符的运行时间的内置代码在O(n ^ 2)中运行。

这个函数怎么能是O(n ^ 2)?

谢谢您的支持。

在循环的每次迭代期间,语句answer += c; 必须每一个字符字符串拷贝已经answer到一个新的字符串。

例如,n = 5,c ='5'

  • 第一个循环: answer是一个空字符串,但它仍然必须创建一个新字符串。 有一个操作可以追加第一个'5' ,而answer现在是"5"
  • 第二个循环: answer现在将指向一个新字符串,第一个'5'复制到一个新字符串,另一个'5'附加,以产生"55" 不仅是一个新的String创建的,一个字符'5'是从以前的字符串复制并另一个'5'被附加。 附加两个字符。
  • 第n个循环: answer现在将指向一个新字符串,将n - 1'5 '5'字符复制到一个新字符串,并附加一个额外的'5'字符,以生成一个包含n 5s的字符串。

复制的字符数为1 + 2 + ... + n = n(n + 1)/ 2。 这是O(n 2 )。

在Java循环中构造这样的字符串的有效方法是使用StringBuilder ,使用一个可变的对象,并且每次在每个循环中追加一个字符时都不需要复制所有字符。 使用StringBuilder的成本为O(n)。

字符串在Java中是不可变的。 我认为这个可怕的代码是O(n ^ 2),因为这个原因。 它必须在每次迭代时构造一个新的String。 我不确定字符串连接是否真正与字符数成线性比例(似乎它应该是一个恒定的时间操作,因为字符串具有已知的长度)。 然而,如果你把作者的话用于它,那么每次迭代迭代n次,花费与n成比例的时间,你得到n ^ 2。 StringBuilder会给你O(n)。

我大多同意在实践中它是O(n ^ 2),但考虑:

Java很聪明。 在许多情况下,它使用StringBuilder而不是字符串来进行连接。 你不能只是假设它每次都要复制底层数组(尽管在这种情况下几乎可以肯定)。

Java一直都是SMARTER。 没有理由不能基于StringBuilder优化整个循环,因为它可以分析你的所有代码,并发现你不会将它用作该循环中的字符串。

可能会发生进一步的优化 - 字符串当前使用一个数组和一个长度和一个共享标志(也许是一个起始位置,因此拆分不需要复制,我忘了,但他们改变了分割实现) - 所以附加到一个超大数组,然后返回一个新的字符串,引用同一个底层数组,但更高端而不改变原始字符串是完全可能的(按照设计,他们在某种程度上做了这样的东西)...

所以我认为真正的问题是,根据语言级构造的特定实现计算O()是一个好主意吗?

虽然我不能肯定地说答案是什么,但我可以说,除非你绝对需要它,否则优先考虑它是O(n ^ 2)是一个非常糟糕的想法 - 你可以采取远离java的能力,以便稍后通过手工优化加速你的代码。

PS。 这是来自经验。 我不得不优化一些作为频谱分析仪用户界面的java代码。 我看到了各种各样的String +操作,并认为我用.append()清理它们。 它节省了没有时间,因为Java已经优化了不在循环中的String +操作。

复杂性变为O(n ^ 2),因为每次字符串将长度增加1并且每次需要n复杂度时创建它。 而且,外环的复杂性为n 所以确切的复杂度将是(n *(n + 1))/ 2 ,即O(n ^ 2)

例如,

对于abcdefg

a // one length string object is created so complexity is 1
ab // similarly complexity is 2
abc // complexity 3 here 
abcd // 4 now.
abcde // ans so on.
abcdef
abcedefg

现在,您看到总复杂度为1 + 2 + 3 + 4 + ... + n =(n *(n + 1))/ 2。 在大O表示法中它是O(n ^ 2)

那是因为:

answer += c;

String连接。 在java中, Strings不可变的

这意味着通过创建原始字符串的副本并将c附加到其来创建连接字符串。 因此,对于n大小的String简单的连接操作是O(n)

在第一次迭代中,答案长度为0 ,在第二次迭代中为1 ,在第二次迭代中为2,依此类推。

所以你每次都在做这些操作,即

1 + 2 + 3 + ... + n = O(n^2)

对于字符串操作, StringBuilder是首选方式,即它在O(1)时间内附加任何字符。

将字符串的长度视为“n”,因此每次我们需要在末尾添加元素时,字符串的迭代为“n”,并且我们还有外部for循环,因此“n”为此,因此结果我们得到O(n ^ 2)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM