1

Let function f() be:

void f(int n)
{
   for (int i=1; i<=n; i++)
     for (int j=1; j<=n*n/i; j+=i)
       printf(“*”);
}

According to my calculations, the run time in Big O method should be O(n2log n).
The answer is O(n2). Why is that?

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • Duplicate: http://stackoverflow.com/questions/362059/what-is-the-big-o-of-a-nested-loop-where-number-of-iterations-in-the-inner-loop – Baronz Feb 13 '16 at 18:40
  • 1
    @Baronz This looks like a pretty different set of loops with a very different analysis. Am I missing something? – templatetypedef Feb 13 '16 at 18:41
  • Big O notation is about the limits as the sizes approach large values. You can just glance at the "shape" of this function, it's a nested for loop. Big O only takes the highest values and no constants, so Dina's loops reduce to the same shape as the duplicate question asked by Simucal – Baronz Feb 13 '16 at 18:44
  • @baronz: asymptotic behaviour is not purely a function of the number of nested loops. – Oliver Charlesworth Feb 13 '16 at 18:46
  • @OliverCharlesworth help me by posting a new comment clarifying, and I'll delete mine so we don't have confusing comments here. I still contend this is a duplicate and solved the same way as the link I posted. Let's agree on how to solve Dina's problem and get any erroneous stuff off the page so we can revisit the page as a reference for us all :) – Baronz Feb 13 '16 at 18:51
  • going back to the original post too: http://stackoverflow.com/questions/526728/time-complexity-of-nested-for-loop – Baronz Feb 13 '16 at 18:52
  • 1
    Isn't it simply that as n gets very large, log n is inconsequential. n^2 is the driving complexity. https://en.wikipedia.org/wiki/Big_O_notation – Baronz Feb 13 '16 at 18:58
  • @Baronz While I agree that the *process* is similar, the specific mathematical techniques you'd need to use here - namely, using the harmonic series - makes this pretty different from the linked question. Understanding the answer to that question will not completely prepare you to answer this one. – templatetypedef Feb 13 '16 at 18:58
  • So, I'll double-down and postulate that there is no such thing as O(n^2 log n) - - I think it ALWAYS simplifies to O(n^2) - @templatetypedef – Baronz Feb 13 '16 at 19:02
  • Look at the difference in growth - http://bigocheatsheet.com/ – Baronz Feb 13 '16 at 19:04
  • @Baronz As someone who teaches courses in algorithms and advanced data structures, I can confirm this isn't the case. :-) As an example, look at the [Karger-Stein algorithm](https://en.wikipedia.org/wiki/Karger%27s_algorithm#Karger.E2.80.93Stein_algorithm), whose runtime is O(n^2 (log n)^3). If O(n^2 log n) runtime didn't exist, the runtime here would not be listed as such. – templatetypedef Feb 13 '16 at 19:08
  • I appreciate the discussion. How can I help clean up this question? I don't want my statements (especially incorrect ones) to distract from whatever is the actual answer. I will concede that you are more of an expert than I am, I've TAKEN a series of algorithm classes, but never taught any. I don't consider myself a thought leader in the field, just someone curious about it – Baronz Feb 13 '16 at 19:13
  • @Baronz Regarding comments: most of the time, comments posted in a discussion-like manner can generally all be removed after the participants have reached an agreement or impasse (or, continued in chat). I frequently discuss similar matters in comments, but readily remove all my comments once the thing being discussed has been sorted out (I will remove this and the following comment once you've read them). Only comments that add something that the Q or A does not already address have reason to "live on". From the above, I'd say all comments could be removed without loss of Q&A-valuable info. – dfrib Feb 14 '16 at 11:14
  • ... To sum it up: _"[Comments are temporary "Post-It" notes left on a question or answer](http://stackoverflow.com/help/privileges/comment)."_. (Generally for an old accepted answer where the answers' author is no where to be seen, and where the answer is now outdated, comments can be valuable). – dfrib Feb 14 '16 at 11:15
  • Dear @Baronz, if n be very very large `O(n^2)` and `O(n^2*log(n))` are not same. Complexity of this question is `O(n^2)` (not `O(n^2*log(n))`). You can see my answer. – Meysam Ghahramani Feb 15 '16 at 04:41

3 Answers3

3

I owe you an apology. I misread your code the first time around, so the initial answer I gave was incorrect. Here's a corrected answer, along with a comparison with the original answer that explains where my analysis went wrong. I hope you find this interesting - I think there's some really cool math that arises from this!

The code you've posted is shown here:

for (int i=1; i<=n; i++)
  for (int j=1; j<=n*n/i; j+=i)
     printf(“*”);

To determine the runtime of this code, let's look at how much work the inner loop does across all iterations. When i = 1, the loop counts up to n2 by ones, so it does n2 work. When i = 2, the loop counts up to n2 / 2 by twos, so it does n2 / 4 work. When i = 3, the loop counts up to n2 / 3 by threes, so it does n2 / 9 work. More generally, the kth iteration does n2 / k2 work, since it counts up to n2 / k with steps of size k.

If we sum up the work done here for i ranging from 1 to n, inclusive, we see that the runtime is

n2 + n2 / 4 + n2 / 9 + n2 / 16 + ... + n2 / n2

= n2 (1 + 1/4 + 1/9 + 1/16 + 1/25 + ... + 1/n2).

The summation here (1 + 1/4 + 1/9 + 1/16 + ...) has the (surprising!) property that, in the limit, it's exactly equal to π2 / 6. In other words, the runtime of your code asymptotically approaches n2 π / 6, so the runtime is O(n2). You can see this by writing a program that compares the number of actual steps against n2 π / 6 and looking at the results.

I got this wrong the first time around because I misread your code as though it were written as

for (int i=1; i<=n; i++)
  for (int j=1; j<=n*n/i; j+=1)
     printf(“*”);

In other words, I thought that the inner loop took steps of size one on each iteration rather than steps of size i. In that case, the work done by the kth iteration of the loop is n2 / k, rather than n2 / k2, which gives a runtime of

n2 + n2/2 + n2/3 + n2/4 + ...n2/n

= n2(1 + 1/2 + 1/3 + 1/4 + ... + 1/n)

Here, we can use the fact that 1 + 1/2 + 1/3 + ... + 1/n is a well-known summation. The nth harmonic number is defined as Hn = 1 + 1/2 + 1/3 + ... + 1/n and it's known that the harmonic numbers obey Hn = Θ(log n), so this version of the code runs in time O(n2 log n). It's interesting how this change so dramatically changes the runtime of the code!

As an interesting generalization, let's suppose that you change the inner loop so that the step size is iε for some ε > 0 (and assuming you round up). In that case, the number of iterations on the kth time through the inner loop will be n2 / k1 + ε, since the upper bound on the loop is n2 / k and you're taking steps of size kε. Via a similar analysis to what we've seen before, the runtime will be

n2 + n2 / 21+ε + n2 / 31+ε + n2 / 31+ε + ... + n2 / n1+ε

= n2(1 + 1/21+ε + 1/31+ε + 1/41+ε + ... + 1/n1+ε)

If you've taken a calculus course, you might recognize that the series

1 + 1/21+ε + 1/31+ε + 1/41+ε + ... + 1/n1+ε

converges to some fixed limit for any ε > 0, meaning that if the step size is any positive power of i, the overall runtime will be O(n2). This means that all of the following pieces of code have runtime O(n2):

for (int i=1; i<=n; i++)
  for (int j=1; j<=n*n/i; j+=i)
     printf(“*”);

for (int i=1; i<=n; i++)
  for (int j=1; j<=n*n/i; j+=i*i)
     printf(“*”);

for (int i=1; i<=n; i++)
  for (int j=1; j<=n*n/i; j+=i*(sqrt(i) + 1))
     printf(“*”);
Community
  • 1
  • 1
templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
1

Run time for first loop is n and Run time for second loop is (n/i)^2 (not n^2/i) because we have j+=i(not j++). So total time is as follow:

∑{i=1to n}(n/i)^2 = n^2∑{i=1to n}(1/i)^2 < 2*n^2

So time complexity is O(n^2)

  • Thanks for pointing out the error I made in my original answer. I've replaced it with a new answer detailing the analysis and a comparison against the original one. – templatetypedef Feb 15 '16 at 07:44
-1

From what I've learned from theory, is that the i does not affect the complexity very much. Since you have an exponential function, the log n would be neglected. Therefore, it would be considered only the big O(n2) instead of the expected O(n2log n).

Recall that when we use big-O notation, we drop constants and low-order terms. This is because when the problem size gets sufficiently large, those terms don't matter. However, this means that two algorithms can have the same big-O time complexity, even though one is always faster than the other. For example, suppose algorithm 1 requires N2 time, and algorithm 2 requires 10 * N2 + N time. For both algorithms, the time is O(N2), but algorithm 1 will always be faster than algorithm 2. In this case, the constants and low-order terms do matter in terms of which algorithm is actually faster.

However, it is important to note that constants do not matter in terms of the question of how an algorithm "scales" (i.e., how does the algorithm's time change when the problem size doubles). Although an algorithm that requires N2 time will always be faster than an algorithm that requires 10*N2 time, for both algorithms, if the problem size doubles, the actual time will quadruple.

When two algorithms have different big-O time complexity, the constants and low-order terms only matter when the problem size is small. For example, even if there are large constants involved, a linear-time algorithm will always eventually be faster than a quadratic-time algorithm. This is illustrated in the following table, which shows the value of 100*N (a time that is linear in N) and the value of N2/100 (a time that is quadratic in N) for some values of N. For values of N less than 104, the quadratic time is smaller than the linear time. However, for all values of N greater than 104, the linear time is smaller.

Take a look at this article for more details.

Simply Me
  • 1,579
  • 11
  • 23
  • You have **n^2 log n**. Here, **log n** is just a constant, it is defined the moment you assign a value to it. And yes, I am sure it is O(n^2). – Simply Me Feb 13 '16 at 18:51
  • Why don't you remove log n from O(nlogn) then? – Dina Benyamin Feb 13 '16 at 18:54
  • You cannot drop log terms from big-O like this. It doesn't work that way. – templatetypedef Feb 13 '16 at 18:55
  • @templatetypedef, I know what you mean, but since it is time complexity, you don't keep the constants. It's all about how the program scales. – Simply Me Feb 13 '16 at 18:59
  • @SimplyMe But the log n term here isn't a constant - it actually does make a difference to the program's runtime. – templatetypedef Feb 13 '16 at 19:03
  • @SimplyMe I agree that if the runtime was something of the form n^2 + log n, then you could absolutely drop the log n term. However, in this case the log n term is multiplied in to the n^2 term because of how the inner loop happens to run. That's why in the case of something like n^2 + n you can drop the n term and in n^2 / 100 you can drop the 1 / 100 term, but in n^2 log n you can't drop the log n term. – templatetypedef Feb 13 '16 at 19:06
  • No, it doesn't! It depends on the context. IF you compare the time complexity of 2 programs and they have the same big O, only then the constant matters. – Simply Me Feb 13 '16 at 19:07
  • @SimplyMe I agree with what you just said. However, I don't actually think that the program given above has runtime O(n^2). Take a look at the math in my answer and the program I wrote that simulates the code and compares the actual number of iterations against the theoretical predictions - there's a very close match that can't easily be explained if the runtime was purely quadratic. – templatetypedef Feb 13 '16 at 19:10
  • @templatetypedef, I agree with you on that, but you only take the constant while comparing the complexities are the same and you need to calculate which is more efficient(here is log n, because it depends on how the program scales. And the log function doesn't affect it due to the exponential one). – Simply Me Feb 13 '16 at 19:12
  • @SimplyMe Perhaps I'm misinterpreting what you're saying here. You've mentioned that the log term doesn't affect anything because there's an exponential term. I don't see an exponential term here (there's a polynomial term, though). Can you elaborate a bit? – templatetypedef Feb 13 '16 at 19:13
  • @templatetypedef, sorry, I agree with you, I indeed intended to say polynomial. The log function has the growth lower than the polynomial function growth. The limit of those 2 functions using l'Hopital's rule will simply transform from *n^2 / log n** to **2*n / (1/n) = 2 * n* n** – Simply Me Feb 13 '16 at 19:17
  • So you can say that when n scales, the function tends to the value n^2 tends to. I agree that you sometimes need to take the constant, but **unless** you compare two algorithms, you should remove the log n. – Simply Me Feb 13 '16 at 19:19
  • I see what you're saying. I think you might be mixing up two different ideas. You're absolutely right that n^2 dominates log n asymptotically. I completely agree with that. The concern I have is that in this case, I don't think you can claim that the runtime is n^2 in the first place. Can you explain where you're getting that the runtime is O(n^2) from for this specific piece of code? – templatetypedef Feb 13 '16 at 19:20
  • O(logN) will never be more than 64. In practice, whenever terms get as 'small' as O(logN), you have to measure to see if the constant factors win out. – Simply Me Feb 13 '16 at 19:22
  • @SimplyMe Please consider: [log(10^100)](http://www.google.se/search?q=log+(10%5E100)) – Fredrik Savje Feb 13 '16 at 19:25
  • @FredrikSavje oh, please... That would result into something under 500. – Simply Me Feb 13 '16 at 19:27
  • Write this on google: **log2(10^100)**. The result is **332.192809489** – Simply Me Feb 13 '16 at 19:28
  • 1
    @SimplyMe You stated that `O(log n)` will never be more than 64, but then claim that log(10^100) is less than 500. Would it not be less than 64 if your statement was true? I think everyone agrees that `O(n^2 log n)` and `O(n^2)` is much the same in practice, but the orders of the functions are not the same. That is to say, there are no `K` and `x` so that `n^2 log n < K * n^2` for all `n > x`. For whatever `K` we choose, for some large enough `n` we always have `log n > K`. Thus we cannot drop the `log n` factor. – Fredrik Savje Feb 13 '16 at 19:32
  • If so, you can say that you have log(10^100000). BUT we talk about programming languages that don't accept that kind of numbers. – Simply Me Feb 13 '16 at 20:14
  • @SimplyMe Yes, in this case the `int` type is bounded by `INT_MAX` so `log_2 n` will never be greater than 31 (assuming a 32-bit `int`). However, by the same logic, the run time will be `O(1)` as both `n^2` and `n^2 log n` will be bounded. Normally, one talks about the complexity of an algorithm rather than the complexity of an implementation (as the latter is restricted by real world constraints). In this case, the OP used an implementation to describe the algorithm, and it's implicit that one should ignore the bound on `int` as the answer is trivial otherwise. – Fredrik Savje Feb 13 '16 at 21:58