1

I'm learning about Big O notation and how it relates to computer science and I understand the sentiment behind Big O, however I'm struggling at understanding how we define a "step".

In the book I'm reading as well as a lot of different articles they use something like below to express an O(N) example since, as N increases we perform more steps linearly:

void printAllElementOfArray(int arr[], int size)
{
    for (int i = 0; i < size; i++)
    {
        printf("%d\n", arr[i]);
    }
}

I understand the purpose of the example wherein for the function above "we perform printf N times" and therefore the algorithm is best represented through f(N) <= O(N) (I think that's the mathematical meaning, I get the sentiment of Big O but not fully there yet on the math).

The piece I'm really struggling with though is why we consider "printf" a single step. printf is in itself its own function with its own sequence of steps and so to that end, does that function not need to be elaborated? Is there any defined criteria of when an inner function is elaborated vs when we define it as a unit step?

I read somewhere that usually for nested functions big O is the multiplication of steps for each function O(g(N) x f(N)).

  • So to that end, is the implication/assumption here that printf is O(1) and therefore the overall algorithm is O(1*N)?

Any clarifications is really appreciated.

Davis Kim
  • 21
  • 3
  • Does this answer your question? [What is O(...) and how do I calculate it?](https://softwareengineering.stackexchange.com/questions/132331/what-is-o-and-how-do-i-calculate-it) – gnat Jan 22 '23 at 20:13
  • @gnat: please enlighten me - where in the answers to that other question did you find a clear explanation what "a step" is? – Doc Brown Jan 22 '23 at 21:44
  • 3
    It is the other way around. You define the step and then you derive how it scales in the worst case (which is what big-O really is) – Thorbjørn Ravn Andersen Jan 23 '23 at 07:48
  • @ThorbjørnRavnAndersen or, to directly answer the title, "However you like" – Caleth Jan 23 '23 at 11:51
  • @Caleth for some reason it appears to me that the question was edited but the history says otherwise. – Thorbjørn Ravn Andersen Jan 23 '23 at 14:24
  • Others are claiming answering "yes" to the Q of whether the book assumes `printf` is `O(1)`, but without seeing the book/context, I'm skeptical that they didn't either A) Make a mistake, or B) Intend *N* to mean "the combined input size". I.e., *N* may not refer to "array length", but total number of characters to print. `O(N)` makes perfect sense if you calculate according to B. – svidgen Jan 23 '23 at 15:27
  • (Although, I suppose [there is some debate about the complexity of printf](https://stackoverflow.com/questions/25828018/time-complexity-of-a-printf) ...) – svidgen Jan 23 '23 at 15:31
  • Hi all, thank you so much for the comments/help in understanding this. I'm glad that it seems there is some nuance/lack of clarity and I wasn't missing something obvious. _ThorbjørnRavnAndersen, yeah I didn't edit the question, not sure why it says I did, I'm new though so maybe I opened it. @svidgen, so just for reference / to answer your question, the book I'm referencing (Data Structures and Algorithms by Jay Wengrow, pg 44) and Im having a tough time writing the code but its just, listing the array (N), looping through the array and printing the result and considers it O(N) – Davis Kim Jan 25 '23 at 01:05

4 Answers4

6

A step is anything that takes time, memory, or whatever you are measuring behind the label of "complexity". A step can be any size, as long as it is roughly the same size each time it gets done, so that you can reasonably say "doing this twice takes twice as much <x> as doing it once".

is the implication/assumption here that printf is O(1)

Yes.

If printf performance scales with some part of the input (for example, the size of the numbers being printed), you might call that "M" and say that the complexity is O(N*M) or O(N*log(M)). But if the scaling you are interested in is "amount of numbers" rather than "size of numbers", you can treat that as a constant factor. You'll find that this is unfortunately done quite often without explicitly stating it.

Jacob Raihle
  • 1,692
  • 13
  • 14
4

is the implication/assumption here that printf is O(1) and therefore the overall algorithm is O(1*N)?

This. I'd say the example you've quoted (which I appreciate you've just taken from somewhere else) isn't a great example precisely because of the uncertainty about this assumption. Perhaps a better example would be something like

int sumArray(int arr[], int size)
{
    int sum = 0;
    for (int i = 0; i < size; i++)
    {
        sum += arr[i];
    }
    return sum;
}

as we're now dealing with a much simpler operation. Ideally, any explanation makes these kind of assumptions clear.

Philip Kendall
  • 22,899
  • 9
  • 58
  • 61
1

You are correct that we need to consider the big-O of printf as well and the teaching material that you got your example from is taking a big shortcut by ignoring that.

Now, looking at printf, a single call to that function is not going to do a different amount of work if you change the size of your array. By that reasoning, printf is O(1) with regard to the part of your algorithm you are interested in. (It might have a different big-O if you, for example, consider the values within the array.)

I read somewhere that usually for nested functions big O is the multiplication of steps for each function O(g(N) x f(N)).

  • So to that end, is the implication/assumption here that printf is O(1) and therefore the overall algorithm is O(1*N)?

Yes, that is completely correct.

I'm struggling at understanding how we define a "step".

It might not be the formal definition, but the easiest way is to consider as a "step" every operation and function call where you know that the amount of work and memory doesn't change when your input size changes..

In other words, a "step" is every operation in your algorithm that you know to be O(1).

Bart van Ingen Schenau
  • 71,712
  • 20
  • 110
  • 179
1

Is there any defined criteria of when an inner function is elaborated vs when we define it as a unit step?

When it depends on N.

So to that end, is the implication/assumption here that printf is O(1) and therefore the overall algorithm is O(1*N)?

Yes, printf doesn't depend on N, so it's O(1), and the N dominates.

Is there any defined criteria of when an inner function is elaborated vs when we define it as a unit step?

One way of looking at it is to just plot the time taken against values of N and see if it looks linear, logarithmic, or whatever. That should help your intuition of which terms are dominating.

Note that this is the asymptotic complexity: it gives the shape of the curve as N shoots off towards infinity.

Let's compare two printf implementations:

  • printf_slow takes a million years to complete and uses a petabyte of working memory, but it is a fixed million years and a fixed petabyte and neither depends on N

  • printf_fast takes log(N) nanoseconds and uses no storage at all

    (it's actually not implausible for printf to take log(N) time, since that's how many digits it needs to format)

Now the first version is constant time and the overall function is linear O(N), but takes about N million years per run.

The second version is logarithmic time and the overall function is O(N log N), which looks asymptotically worse, even though int size cannot store a large enough value for N that the second function will ever take longer to execute.

If we allow N to really shoot off towards infinity (ie, increase without bound assuming no storage constraints for size itself), then the second version will eventually take longer than the first - I think when size is around 10 to the power of 31 sextillion.

Useless
  • 12,380
  • 2
  • 34
  • 46
  • Note that the N for `printf_fast` is a different N than the one for `printAllElements`. So the overall function wouldn't be `O(N log N)`, it would be `O(N log M)` where M is the largest `arr[i]`, if it is related to the number of digits to be printed. – Jasmijn Jan 27 '23 at 10:30