2

I've been doing a homework sheet for a while now, and there is a massive discrepancy between what I think the asymptomatic complexity is and what the runtime result suggests.

Below is a table for the runtime for the program.

|      Input Size     |     Runtime (Seconds)   |
|---------------------|-------------------------|
|       10000         |      0.040533803        |
|---------------------|-------------------------|
|       20000         |      0.154712122        |
|---------------------|-------------------------|
|        30000        |      0.330814060        |    
|---------------------|-------------------------|
|        40000        |      0.603440983        |    
|---------------------|-------------------------|
|        50000        |      0.969272780        |    
|---------------------|-------------------------|
|        60000        |      1.448454467        |

Runtime (Seconds) Against Input Size

string = "";
newLetter = "a";
for (int i = 500; i < n; i++) {
    string = string + newLetter;
}
return string

Why would there be a discrepancy between the complexity of the algorithm and the growth of the runtime apart from me being wrong?

From the runtime results, it looks like the program has a time complexity of O(n2). Doubling the input size seems to increase the runtime by a factor of 4, which would suggest a quadratic function?

But looking at the program itself, I'm 99.9% certain that the time complexity is actually O(n).

Is it possible that there's an extraneous reason for this discrepancy? Is there any possibility that the runtime results are indeed linear?

My best guess for this discrepancy is that the for loop makes the next iteration slower (which, looking at the program makes sense as I think the Java compiler would have to iterate every additional newLetter given) but that would still be linear no? It's not a nested for loop.

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • 1
    If you don't share the program, or a contrived version of the relevant loop, it's impossible to say. Voting to close as requiring a [mcve]. Having said that, complexity and actual runtime are completely different things. Complexity is about the theoretical rate of growth of an algorithm and actual runtime is specific to a concrete set of hardware and runtime conditions. Addressing your questions, yes, this chart shows a quadratic function, not a linear one. If anything, running a loop multiple times typically speeds it up thanks to prediction/speculation/cache hits. – ggorlen Oct 13 '20 at 16:48
  • I'll edit in a contrived version similar to the loop I was given. –  Oct 13 '20 at 16:52
  • 1
    It's still ambiguous but the algorithm you've added in can be O(n^2) because of [Schlemiel the painter's algorithm](https://en.wikichip.org/wiki/schlemiel_the_painter%27s_algorithm). Often, programs will detect this pattern and optimize it to O(1) but that appears to not be the case in whatever runtime you're dealing with--it's walking the string from the start on every `+=`, so this inner operation is O(n). – ggorlen Oct 13 '20 at 16:59

1 Answers1

3

The code you've written actually does run in time Θ(n2). The reason has to do with this part of the code:

string = string + newLetter;

Here, your intent is to say "please append a new character to the end of this string." However, what Java actually does is the following:

  1. Evaluate the expression string + newLetter. This makes a brand-new string formed by copying the full contents of string, then appending newLetter to the end of that new string.
  2. Assign the result to string.

The issue here is that step (1) takes longer and longer to execute the longer the string is, since all the characters have to be copied over. In particular, if string has length k, then step (1) takes time Θ(k) because k characters must be copied. Since the length of string matches the current loop iteration index, this means the work done is

Θ(1 + 2 + 3 + ... + n)

= Θ(n(n+1) / 2)

= Θ(n2),

which is why you're seeing the plot you're seeing.

You can speed this up by using StringBuilder rather than String to assemble the larger string. It doesn't make all the intermediary copies as it goes, and instead maintains an internal array that can grow as needed.

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065