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

时间:2018-07-30 16:25:45

标签: java algorithm big-o

这是来自算法书“ Java中的数据结构和算法,第六版”的代码。由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的命令是答案=(answer + c)的简写。这是     命令不会导致将新字符添加到现有字符串中     实例而是生成具有所需序列的新String     字符,然后重新分配变量,以引用该新变量     串。在效率方面,这种解释的问题在于     由于连接而创建新字符串需要时间     与结果字符串的长度成正比。第一次     通过该循环,结果为长度1,第二次通过循环     结果的长度为2,依此类推,直到达到长度的最后一个字符串     n。”

但是,我不明白,该代码如何具有O(n ^ 2),因为其原始操作数仅会使每次迭代加倍,而与n的值无关(j

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

该函数如何为O(n ^ 2)?

感谢您的支持。

6 个答案:

答案 0 :(得分:2)

在循环的每次迭代中,语句if必须将字符串answer += c;中已经存在的每个字符复制到新字符串中。

例如n = 5,c ='5'

  • 第一个循环:answer是一个空字符串,但仍必须创建一个新字符串。有一个操作可以附加第一个answer,而'5'现在是answer
  • 第二个循环:"5"现在指向一个新字符串,将第一个answer复制到一个新字符串,并附加另一个'5',以制作'5'。不仅会创建新的"55",还会从先前的字符串中复制一个字符String,并附加另一个'5'。附加两个字符。
  • “ n”个循环:'5'现在指向一个新字符串,其中 n-1 answer个字符被复制到一个新字符串中,另外还有{{附加1}}个字符,以组成一个 n 5s的字符串。

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

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

答案 1 :(得分:2)

字符串在Java中是不可变的。出于这个原因,我相信这个糟糕的代码就是O(n ^ 2)。它必须在每次迭代中构造一个新的String。我不确定字符串连接是否确实与字符数成线性比例(由于字符串的长度已知,因此似乎应该是恒定时间的操作)。但是,如果使用作者的话,然后迭代n次,每次迭代所花的时间与n成正比,则得到n ^ 2。 StringBuilder会给您O(n)。

答案 2 :(得分:2)

我大体上同意在实践中将其设为O(n ^ 2),但请考虑:

Java是SMART。在许多情况下,它在后台使用StringBuilder代替字符串进行串联。您不能仅仅假设它每次都会复制底层数组(尽管在这种情况下几乎可以肯定会复制)。

Java总是很聪明。没有理由不能优化基于StringBuilder的整个循环,因为它可以分析您的所有代码并弄清楚您没有将其用作该循环内的字符串。

可能发生进一步的优化-字符串当前使用数组,长度和共享标志(也许是起始位置,因此拆分不需要复制,我忘了,但是无论如何它们都会更改拆分实现)-因此追加到一个超大的数组中,然后返回一个引用相同底层数组的新字符串,但更高端而又不改变原始字符串是完全有可能的(根据设计,它们已经在某种程度上做到了这一点)……

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

尽管我不能肯定地说出答案是什么,但我可以说,除非您绝对需要它,否则在O(n ^ 2)的假设下进行优化将是一个非常糟糕的主意-您可以通过今天的手动优化取消Java的加速代码功能。

ps。这是根据经验。我必须优化一些Java代码,这是频谱分析仪的UI。我看到了各种各样的String +操作,并弄清楚我会使用.append()清理它们。它节省了时间,因为Java已经优化了不在循环中的String +操作。

答案 3 :(得分:1)

复杂度变为 O(n ^ 2),因为字符串每次将长度增加一,并在每次需要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)

答案 4 :(得分:0)

那是因为:

$lorem_text .= "lorem\n";
$lorem_outside_text = '';

for ($i = 0; $i <= 5; $i++) {
  $lorem_outside_text .= $lorem_text;
}

echo $lorem_outside_text;

answer += c; 的串联。在Java中,String 不可变

这意味着通过创建原始字符串的副本并向其附加Strings来创建串联的字符串。因此,对于大小为c的{​​{1}},O(n)是一个简单的串联操作。

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

所以您每次都在执行这些操作

0

对于字符串操作,1是首选的方式,即,它在1 + 2 + 3 + ... + n = O(n^2) 时间后附加任何字符。

答案 5 :(得分:0)

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