Java 这段简单代码的复杂性是什么?

Java 这段简单代码的复杂性是什么?,java,complexity-theory,big-o,time-complexity,stringbuffer,Java,Complexity Theory,Big O,Time Complexity,Stringbuffer,我正在粘贴一本电子书中的文本。它说明了O(n2)的复杂性,并给出了解释,但我不知道如何解释 问题:这段代码的运行时间是多少 public String makeSentence(String[] words) { StringBuffer sentence = new StringBuffer(); for (String w : words) sentence.append(w); return sentence.toString(); } 1 public Stri

我正在粘贴一本电子书中的文本。它说明了O(n2)的复杂性,并给出了解释,但我不知道如何解释

问题:这段代码的运行时间是多少

public String makeSentence(String[] words) {
    StringBuffer sentence = new StringBuffer();
    for (String w : words) sentence.append(w);
    return sentence.toString();
}
1 public String makeSentence(String[] words) {
2 StringBuffer sentence = new StringBuffer();
3 for (String w : words) sentence.append(w);
4 return sentence.toString();
5 }
public String makeSentence(String[] words) {
    String sentence = new String("");
    for (String w : words) sentence+=W;
    return sentence;
}
书中给出的答案是:

O(n2),其中n是句子中的字母数。原因如下:每次你 在句子中附加一个字符串,就可以创建一个句子的副本,并遍历其中的所有字母 语句,以便在每次需要在 循环,并且您至少循环了n次,这将为您提供一个O(n2)运行时间。哎哟


有人能更清楚地解释这个答案吗?

这实际上取决于
StringBuffer
的实现。假设
.append()
是常数时间,很明显,您在时间上有一个
O(n)
算法,其中
n=单词数组的长度。如果
.append
不是常数时间,则需要将O(n)乘以方法的时间复杂度。如果
StringBuffer
的当前实现确实逐字符复制字符串,则上述算法是

Θ(n*m)
O(n*m)
,其中
n
是字数,
m
是平均字长,您的书是错的。我猜你在寻找一个严格的界限

书中答案不正确的简单示例:
String[]words=['alphabet']
根据本书的定义,
n=8
,因此该算法将由64个步骤限定。是这样吗?显然不严格。我看到一个赋值操作和一个复制操作有n个字符,所以你可以得到9个步骤。这类行为是由
O(n*m)
的边界预测的,如我上面所示

我做了一些挖掘,这显然不是一个简单的字符副本。看起来内存正在被批量复制,这让我们回到了
O(n)
,这是您对解决方案的第一个猜测

/* StringBuffer is just a proxy */
public AbstractStringBuilder append(String str) 
{
        if (str == null) str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
}

/* java.lang.String */
void getChars(char dst[], int dstBegin) {
             System.arraycopy(value, offset, dst, dstBegin, count);
}

你的书要么很旧,要么很糟糕,要么两者兼而有之。我还没有下定决心深入研究JDK版本,以找到一个不太理想的StringBuffer实现,但也许有一个实现是存在的。

公认的答案是错误的
StringBuffer
已经摊销了O(1)个append,所以n个append将是O(n)

如果不是O(1)append,
StringBuffer
就没有存在的理由,因为用普通的
String
串联来编写循环也是O(n^2)

在我看来像O(n)(其中
n
是所有单词中的字母总数)。基本上,您正在迭代
单词中的每个字符,以将其附加到
字符串缓冲区中

我认为这是O(n^2)的唯一方法是在追加任何新字符之前,如果
append()
迭代缓冲区中的所有内容。如果字符数超过当前分配的缓冲区长度(它必须分配一个新的缓冲区,然后将当前缓冲区中的所有内容复制到新的缓冲区),它实际上偶尔也会这样做。但它不会在每次迭代中都发生,所以您仍然不会有O(n^2)

最多有O(m*n),其中
m
是缓冲区长度增加的次数。由于
StringBuffer
每次分配更大的缓冲区时都会发生变化,我们可以确定
m
大致等于
log2(n)
(实际上
log2(n)-log2(16)
,因为默认的初始缓冲区大小是16而不是1)

因此,真正的答案是这本书的例子是O(n logn),通过预先分配一个容量足以容纳所有字母的
StringBuffer
,您可以将其归结为O(n)

请注意,在Java中,使用
+=
附加到字符串确实会表现出本书解释中描述的低效行为,因为它必须分配一个新字符串,并将两个字符串中的所有数据复制到该字符串中。如果你这样做,它是O(n^2):


但是使用
StringBuffer
不应产生与上述示例相同的行为。这是
StringBuffer
首先存在的主要原因之一。

我试图用这个程序检查它

public class Test {

    private static String[] create(int n) {
        String[] res = new String[n];
        for (int i = 0; i < n; i++) {
            res[i] = "abcdefghijklmnopqrst";
        }
        return res;
    }
    private static String makeSentence(String[] words) {
        StringBuffer sentence = new StringBuffer();
        for (String w : words) sentence.append(w);
        return sentence.toString();
    }


    public static void main(String[] args) {
        String[] ar = create(Integer.parseInt(args[0]));
        long begin = System.currentTimeMillis();
        String res = makeSentence(ar);
        System.out.println(System.currentTimeMillis() - begin);
    }
}
公共类测试{
私有静态字符串[]创建(int n){
字符串[]res=新字符串[n];
对于(int i=0;i
结果是,正如预期的那样,O(n):

java测试200000-128毫秒

java测试500000-370毫秒

java测试1000000-698毫秒


版本1.6.0.21

当以抽象出实现细节的高级别编写此代码时,要回答有关此代码复杂性的问题有点困难。在
append
函数的复杂性方面,似乎没有提供任何保证。正如其他人指出的那样,
StringBuffer
类可以(也应该)编写,以便附加字符串的复杂性不依赖于
StringBuffer
中保存的字符串的当前长度

然而,我怀疑,简单地说“你的书错了!”对提出这个问题的人没有多大帮助——相反,让我们看看正在做出哪些假设,并弄清楚作者想要说什么

您可以做出以下假设:

  • 创建新的字符串缓冲区是O(1)
  • 1 public String makeSentence(String[] words) { 2 StringBuffer sentence = new StringBuffer(); 3 for (String w : words) sentence.append(w); 4 return sentence.toString(); 5 }
    public String makeSentence(String[] words) {
        String sentence = new String("");
        for (String w : words) sentence+=W;
        return sentence;
    }
    
    public String makeSentence(String[] words) {
        StringBuffer sentence = new StringBuffer();
        for (String w : words) sentence.append(w);
        return sentence.toString();
    }
    
    public String makeSentence(String[] words) {
        String sentence = new String();
        for (String w : words) sentence += w;
        return sentence;
    }
    
    public String makeSentence(String[] words) {
        StringBuffer sentence = new StringBuffer();
        for (String w : words) sentence.append(w);
        return sentence.toString();
    }