VOOZH about

URL: https://www.baeldung.com/cs/big-o-notation

โ‡ฑ An Introduction to the Theory of Asymptotic Notations | Baeldung on Computer Science


Baeldung Pro โ€“ CS โ€“ NPI EA (cat = Baeldung on Computer Science)
๐Ÿ‘ announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

1. Overview

In this tutorial, weโ€™ll give an introduction to asymptotic notations, as well as show examples for each of them. Weโ€™ll be discussing Big ๐Ÿ‘ Rendered by QuickLaTeX.com
(Theta), Big ๐Ÿ‘ Rendered by QuickLaTeX.com
(Omega), and Big O notations.

2. Evaluating an Algorithm

In computer science and programming, developers often face code efficiency problems. Asymptotic notations and especially the Big O notation help predict and reason about the efficiency of an algorithm. This knowledge can also affect designing an algorithm based on its goal and desirable performance.

Imagine that we have some code that works on a small subset of data. In the nearest future, weโ€™re expecting an increase in the data, and we need to analyze if our algorithm would be performant enough. Letโ€™s imagine what the algorithm could look like in pseudocode:

algorithm FindElement(list, element):
 // INPUT
 // list = A list of elements
 // element = The element to search for in the list
 // OUTPUT
 // true if the element is in the list, false otherwise
 for listElement in list:
 if listElement = element:
 return true
 return false

The piece of code is quite simple. It just checks if an element is in the list. Letโ€™s try to evaluate its performance. To do that, we need to find out how this algorithm will change if we change the size of our input.

How can we approach measuring the efficiency and performance of this algorithm? We can use time, which can provide us with some understanding of the performance. However, time will depend on the amount of data, the machine weโ€™re running it on, and implementation details. Thus, it would not be helpful for us.

We can count the number of steps require for this code to complete and try to base our judgment on this. This approach will better show us the complexity of an algorithm. However, counting all the steps will depend heavily on the implementation. If we add a step, it will change the measurement of the complexity of an algorithm.

The best approach is to consider that all the elements have the same time to be processed. Letโ€™s assume that we process an element in this list in one basic unit. Thus if we have an element that weโ€™re looking for in the middle of the list, weโ€™ll spend ๐Ÿ‘ Rendered by QuickLaTeX.com
units of time.

In the best-case scenario, the element weโ€™re looking for will be the first one, and weโ€™ll check only the first one. In the worst case โ€“ this element wonโ€™t be there, and weโ€™ll have to check all the elements in the list. If the elements are distributed randomly, on average, weโ€™ll need to go through half of the list to find the required element.

3. Big O Notation

In the example above, the time weโ€™ll spend on the search will depend on the location of the element weโ€™re looking for. However, we can tell exactly how many elements we need to check for the worst-case scenario. The Big O notation represents this worst-case scenario.

3.1. Formal Definition

In essence, Big O notation defines a function that will always be greater than the result of its bounding function. As weโ€™re interested only in the complexity of an algorithm, we can scale ๐Ÿ‘ Rendered by QuickLaTeX.com
by any constant.

Definition: ๐Ÿ‘ Rendered by QuickLaTeX.com
means that there exist two positive constants, ๐Ÿ‘ Rendered by QuickLaTeX.com
and ๐Ÿ‘ Rendered by QuickLaTeX.com
, such that ๐Ÿ‘ Rendered by QuickLaTeX.com
for all ๐Ÿ‘ Rendered by QuickLaTeX.com
.

3.2. Example

Letโ€™s assume that we have a piece of code that sorts some list, prints it, and then writes it to a file.

algorithm SortAndPrint(list):
 // INPUT
 // list = A list of elements
 // OUTPUT
 // Sorts the list, prints each element, and then writes the list to a file
 bubbleSort(list)
 for element in list:
 print(element)
 writeToFile(list)

We can define the complexity of this algorithm. It will be a sum of several parts of the code in our case. Bubble sort has ๐Ÿ‘ Rendered by QuickLaTeX.com
complexity. Printing all the elements will have ๐Ÿ‘ Rendered by QuickLaTeX.com
. Writing to a file, we can assume as a constant time operation. We can assume that it will be 3. Therefore, the overall complexity of this piece of code will be:

๐Ÿ‘ Rendered by QuickLaTeX.com

Visually, itโ€™s represented by the graph below:

๐Ÿ‘ qauratic full small 1

First of all, we should simplify the function. We should drop all the lower order components as they wonโ€™t significantly change when n approaches big values. For example, if we assume n as ๐Ÿ‘ Rendered by QuickLaTeX.com
, the equation will look like this:

๐Ÿ‘ Rendered by QuickLaTeX.com

The second and the third components will comprise a dramatically small part of the overall result. Thus we can drop them. Sometimes, itโ€™s hard to define the higher-order component in more complex functions. Reference this table in case of any doubts.

After simplification, we can reduce the function to ๐Ÿ‘ Rendered by QuickLaTeX.com
. Thus, the previous graph will change a bit, and the final version will be:

๐Ÿ‘ quadratic small 1

3.3. Big O Graphical Representation

As we already defined, the Big O notation represents the worst-case scenario. It will be a function higher than our function from a specific ๐Ÿ‘ Rendered by QuickLaTeX.com
to infinity.

Letโ€™s make a faulty assumption and decide to use a linear function for Big O notation:

๐Ÿ‘ small comparison with linear 1

We compared our quadratic function with several linear: ๐Ÿ‘ Rendered by QuickLaTeX.com
, ๐Ÿ‘ Rendered by QuickLaTeX.com
, ๐Ÿ‘ Rendered by QuickLaTeX.com
, and ๐Ÿ‘ Rendered by QuickLaTeX.com
. However, because a quadratic functionโ€™s growth rate is greater than linear, itโ€™s impossible to cap it with linear. Also, we used a constant multiplier for a function because weโ€™re interested in the rate of growth which depends on the input. A constant, in this case, wonโ€™t make any difference.

Before checking the right solution, we can try another one that wonโ€™t work. We can try to cap our current quadratic function with a cubic one:

๐Ÿ‘ Rendered by QuickLaTeX.com

We can see overlapping graphs below:

๐Ÿ‘ small quadratic to cubic 1

As we can observe, cubic function caps quadratic function from 1 to infinity. We donโ€™t care if the quadratic function, in this case, always has a higher value when ๐Ÿ‘ Rendered by QuickLaTeX.com
.
Therefore, the cubic function completely fits the definition. However,  we also should consider the tightness of the function, and in this case, the cubic function overshoots a lot. The comparison function should be as close to the given function as possible. Despite meeting all the requirements, we cannot use it to define complexity.

Letโ€™s try to cap this function with a quadratic one:

๐Ÿ‘ Rendered by QuickLaTeX.com

The red graph represents our initial function, and the blue one represents the comparison function:

๐Ÿ‘ small quadratic big o 1

As we can see from the graph, the blue line representing our comparison function is always above our initial function. Thatโ€™s why we can use this function for Big O notation. We should drop the constant multiplier as weโ€™re only interested in the growth rate, not a specific value.

Thus, the Big O of our initial code block will be ๐Ÿ‘ Rendered by QuickLaTeX.com
.
In most cases, the Big O of an equation will be the highest order component with all the constant multipliers dropped.

It is worth repeating that the comparison function should be as tight as possible to the function we want to estimate.

4. Big ๐Ÿ‘ Rendered by QuickLaTeX.com
 (Omega)

After learning about Big O notation, Big ๐Ÿ‘ Rendered by QuickLaTeX.com
should be easier to grasp. This asymptotic notation represents a lower bound of a given function. It means the best-case scenario for a function or an algorithm. However, considering that the function should be as tight as possible, we cannot go too optimistic.

Letโ€™s consider the example from the beginning:

algorithm SortAndPrint(list):
 // INPUT
 // list = A list of elements
 // OUTPUT
 // Sorts the list, prints each element, and writes the list to a file
 bubbleSort(list)
 for element in list:
 print(element)
 writeToFile(list)

The complexity of this function is quadratic and, after simplification, will look the same as in the first example:

๐Ÿ‘ quadratic small 1

The best-case scenario is when all the elements are sorted already, and bubble sort will only go through the list once. Thus, we can assume that, theoretically, the best-case scenario will have linear complexity:

๐Ÿ‘ quadratic omega

However, as mentioned before, even though this comparison function meets all the criteria, itโ€™s not tight enough. We cannot assume that all the elements in a given list will be sorted in all the cases in real life. Thatโ€™s why we need to find a function that will be as tight as possible to the current one:

๐Ÿ‘ approaching omega

The correct Big ๐Ÿ‘ Rendered by QuickLaTeX.com
for the given code will be of quadratic complexity, ๐Ÿ‘ Rendered by QuickLaTeX.com
.

5. Big ๐Ÿ‘ Rendered by QuickLaTeX.com
(Theta)

Big ๐Ÿ‘ Rendered by QuickLaTeX.com
notation is the combination of both above.
For the Big ๐Ÿ‘ Rendered by QuickLaTeX.com
, we need to use the function that perfectly fits the given function. To simplify, Big ๐Ÿ‘ Rendered by QuickLaTeX.com
can be described as some function that, with the use of constant multipliers, can represent Big O and Big ๐Ÿ‘ Rendered by QuickLaTeX.com
at the same time:

๐Ÿ‘ big theta 2

Two quadratic functions with different constant multipliers (0.8 and 1.2, respectively) contain inside the given function. Thus, the Big ๐Ÿ‘ Rendered by QuickLaTeX.com
will also be of quadratic complexity.

6. Conclusion

This article taught us about asymptotic notations and why they are essential and valuable in computer science. Even a basic understanding of their use can help create better software.

These notations also help to think about algorithms in terms of efficiency and complexity. Even though modern machinesโ€™ hardware and computing power increased dramatically, so did the datasets these machines operate on. In most cases, itโ€™s enough to concentrate on Big O notation for the accessing algorithms.

We can find a more practical look at this topic implemented in Java as well.