Data Structures and Algorithms using Python
Copyright © 2023 by Rance Necaise
Table Of Contents
Data Structures and Algorithms using Python
Copyright © 2023
by Rance Necaise

5.2 Big-O Notation

Instead of counting the precise number of operations or steps, computer scientists are more interested in classifying an algorithm based on the order of magnitude as applied to execution time or space requirements. This classification approximates the actual number of required steps for execution or the actual storage requirements in terms of variable-sized data sets. The term big-O, which is derived from the expression "on the order of," is used to specify an algorithm's classification.

Defining Big-O

Assume we have a function T(n) that represents the approximate number of steps required by an algorithm for an input of size n. For the second version of our algorithm in the previous section, this would be written as

Now, suppose there exists a function f(n) defined for the integers n0, such that for some constant c, and some constant m,

for all sufficiently large values of nm. Then, such an algorithm is said to have a time-complexity of, or executes on the order of, f(n) relative to the number of operations it requires. In other words, there is a positive integer m and a constant c (constant of proportionality) such that for all n ≥ m, T(n)  ≤  c f(n). The function f(n) indicates the rate of growth at which the run time of an algorithm increases as the input size, n, increases. To specify the time-complexity of an algorithm, which runs on the order of f(n), we use the notation

Consider the two versions of our algorithm from earlier. For version one, the time was computed to be T 1 ( n ) = 2 n 2 . If we let c = 2, then

for a result of O(n2). For version two, we computed a time of T 2 ( n ) = n 2 + n . Again, if we let c = 2, then

for a result of O(n2). In this case, the choice of c comes from the observation that when n ≥ 1, we have n n 2 and n 2 + n n 2 + n 2 , which satisfies the equation in the definition of big-O.

The function f(n) = n2 is not the only choice for satisfying the condition T(n) ≤ c f(n). We could have said the algorithms had a run time of O(n3) or O(n4) since 2n2 ≤ n3 and 2n2 ≤ n4 when n > 1. The objective, however, is to find a function f(·) that provides the tightest (lowest) upper bound or limit for the run time of an algorithm. The big-O notation is intended to indicate an algorithm's efficiency for large values of n. There is usually little difference in the execution times of algorithms when n is small.

Constant of Proportionality

The constant of proportionality is only crucial when two algorithms have the same f(n). It usually makes no difference when comparing algorithms whose growth rates are of different magnitudes. Suppose we have two algorithms, L1 and L2, with run times equal to n2 and 2n respectively. L1 has a time-complexity of O(n2) with c = 1 and L2 has a time of O(n) with c = 2. Even though L1 has a smaller constant of proportionality, L1 is still slower and, in fact an order of magnitude slower for large values of n. Thus, f(n) dominates the expression c f(n) and the run time performance of the algorithm. The differences between the run times of these two algorithms is shown numerically in Table 5.2.1 and graphically in Figure 5.2.1.

nn22n
10 100 20
100 10,000 200
1000 1,000,000 2,000
10000 100,000,000 20,000
100000 10,000,000,000 200,000
Table 5.2.1: Graphical comparison of the data from Figure 5.1.2

Figure 5.2.1: Graphical comparison of the data from Table 5.1.2

Constructing T(n)

Instead of counting the number of logical comparisons or arithmetic operations, we evaluate an algorithm by considering every operation. For simplicity, we assume that each basic operation or statement, at the abstract level, takes the same amount of time and, thus, each is assumed to cost constant time. The total number of operations required by an algorithm can be computed as a sum of the times required to perform each step:

The steps requiring constant time are generally omitted since they eventually become part of the constant of proportionality. Consider Figure 5.2.2 (a), which shows a markup of version one of the algorithm from earlier. The basic operations are marked with a constant time while the loops are marked with the appropriate total number of iterations. Figure 5.2.2 (b) shows the same algorithm but with the constant steps omitted since these operations are independent of the data set size.

Figure 5.2.2: Markup for version one of the matrix summing algorithm: (a) all operations are shown, and (b) only the non-constant time steps are shown.

Choosing the Function

The function f(n) used to categorize a particular algorithm is chosen to be the dominant term within T(n). That is, the term that is so large for big values of n, that we can ignore the other terms when computing a big-O value. For example, in the expression

the term n2 dominates the other terms since for n ≥ 3, we have

which leads to a time-complexity of O(n2). Now, consider the function T(n) = 2n2 + 15n + 500 and assume it is the polynomial that represents the exact number of instructions required to execute some algorithm. For small values of n (less than 16), the constant value 500 dominates the function, but what happens as n gets larger, say 100,000? The term n2 becomes the dominant term, with the other two becoming less significant in computing the final result.

Classes of Algorithms

We will work with many different algorithms in this text, but most will have a time-complexity selected from among a common set of functions, which are listed in Table 5.2.2 and illustrated graphically in Figure 5.2.3.

Algorithms can be classified based on their big-O function. The various classes are commonly named based upon the dominant term. A logarithmic algorithm is any algorithm whose time-complexity is O ( log a n ) . These algorithms are generally very efficient since log a n will increase more slowly than n. For many problems encountered in computer science a will typically equal 2 and thus we use the notation log n to to imply log 2 n . Logarithms of other bases will be explicitly stated. Polynomial algorithms with an efficiency expressed as a polynomial of the form

are characterized by a time-complexity of O ( n m ) since the dominant term is the highest power of n. The most common polynomial algorithms are linear (m = 1), quadratic (m = 2), and cubic (m = 3). An algorithm whose efficiency is characterized by a dominant term in the form an is called exponential. Exponential algorithms are among the worst algorithms in terms of time-complexity.

f (· )Command Name
1 constant
log n logarithmic
n linear
n log n log linear
n2 quadratic
n3 cubic
an exponential
Table 5.2.2: Common big-O functions listed from smallest to largest order of magnitude.

Figure 5.2.3: Growth rates of the common time-complexity functions.
Page last modified on August 26, 2023, at 07:29 AM