Study Guides for Discrete Mathematics

Section 1.8 – The Growth of Functions

In this section we are concerned with determining how fast functions grow and how fast they grow in comparison to one another. For example if we consider the functions f(x) = 2*x + 1 and f2(x) = x2 we can plot a table of values comparing x to f1(x) and f2(x).

 

X

1

2

3

4

5

6

7

8

9

F1(x)

3

5

7

9

11

13

15

17

19

F2(x)

1

4

9

16

25

36

49

64

81

 

The table above is quite useful for understanding function growth. Note that as x increases both f1(x) and f2(x) appear to increase at a faster rate than x. However, there is something very different about f1 and f2’s growth. Regardless of what column we look at, f1(x) is always one more than twice x. f2(x), however, is twice x in column 2, 4 times x in column 4 … and 9 times x in column 9. The ratio of f2(x)/x increases with every column.

 

Big O notation and analysis

Let’s compare f1(x) with x as follows. Even though x is always smaller than f1(x) we can find a constant, C, (namely 3) such that f1(x) <= C*x. Because x and f1(x) always differ by less than a constant multiple we consider them to be comparable in growth. However, there is no constant C such that f2(x) <= C*x. Even if we pick a very large constant such as C=1,000,000 we know that if we look at column 1,000,001 f2(x) will be larger than 1,000,000 *x. In fact in column 1,000,001, f2(x) is 1,000,001 times larger than x. Similarly there is no constant C such that f2(x) <= C*f1(x) since the ratio of f2(x)/f1(x) grows larger with every column. Thus, f2 grows at a faster rate than f1.

 

It is instructional to note that f1(x) is actually larger than f2(x) for columns 1 and 2. However, f2(x) becomes larger in column 3 and becomes increasingly larger in each succeeding column.

Hence, f1(x) <= f2(x) when x > 2.

 

Big O is used as a rough measure of how fast one function grows with respect to another. We say that function f is big O of function g if and only if f grows no faster than some constant multiple of g. We write this as "f is O(g)." The precise definition of big O as given in your text also accounts for the fact that the larger function may not be larger for the first k columns where k is some constant.

 

In the example given above we may state correctly that f1(x) is O(x), x is O(f1(x)) and that f1(x) is O(f2(x)). We may not state that f2(x) is O(f1(x)).

 

Examples:

1. Compare the functions f1(x) = 100*x2 and f2(x) = x3.

F1(x) is O(f2(x) since f1(x) <= 100*f2(x) whenever x >=1

 

2. Compare the functions f1(x) = x and f2(x) = x1/2

f2(x) is O(f1(x)) since x1/2 <= x whenever x >= 1.

 

 

Theorem 2 and Theorem 3 on page 86 give formulas for O(f + g) and O(f * g). I restate them below.

 

Theorem 2:

If f1(x) is O(g1(x)) and f2(x) is O(g2(x)) then f1(x) + f2(x) is O(max(g1(x), g2(x)).

 

Theorem 3:

If f1(x) is O(g1(x)) and f2(x) is O(g2(x)) then f1(x) * f2(x) is O(g1(x)*g2(x))

 

Examples:

1. Give a big O estimate for f, where f is x + x1/2.

x is O(x) and x1/2 is O(x1/2)

Therefore x + x1/2 is O(max(x + x1/2)) = O(x)

 

2. Give a big O estimate for f, where f is x * x1/2.

x is O(x) and x1/2 is O(x1/2)

Therefore x * x1/2 is O(x * x1/2) = O(x3/2)

 

3. Give a big O estimate for the function f(x) = (N2 + N*log(N))*(N3 + N + 1)

By the addition rule N2 + N*log(N) is O(N2)

By the addition rule N3 + N + 1 is O(N3)

Therefore f(x) is O(N2 * N3)

Therefore by the multiplication rule f(x) is O(N5 )

 

Why big O notation and analyses are important.

The time required for most computer programs to run may usually be determined by a function such as N2 or N*log(N) or 2N, where N represents the size of the program input. For example, some programs which sort data require C1*N2 time to run where N is the number of data items to be sorted and C1 is a constant. Other sorting programs require only C2*N*log(N) where N is still the number of data items and C2 is another constant. Let’s observe the running time of two such programs where C1 = 2 and C2 = 10 for various size data sets.

 

Number of data items

N

8

16

32

64

128

256

512

1024

2048

Program

Running time for sort program

2* N2

128

512

2048

8192

32768

131072

524288

2097152

8388608

10*N*log(N)

240

640

1600

3840

8960

20480

46080

102400

225280

 

Note that the 2*N2 program runs faster for the smallest data sets of size 8 and 16. For all larger data sets listed in the table the 10*N*log(N) program runs faster. And most importantly, the larger the data set the bigger the difference. This knowledge can be very valuable in selecting an algorithm for solving a problem such as sorting numbers. In the example above we might conclude that if the data sets to be sorted contain 25 items or less we should probably opt for the 2* N2 program since it is faster in this range of input items and is probably much easier to code.

 

However, if our data sets tend to be larger than this, say, 2048 data items then it is well worth the effort to employ the more complicated 10*N*log(N) program since it runs almost 40 times faster for data sets of this size. In practice our data sets may consist of many hundreds of thousands of items in which case the slower program could take years or even centuries to complete whereas the faster program might complete in a few hours.

 

Time units in the real world.

Note that in the table above no actual time units were given. The actual size of the time unit depends upon many factors such as the nature of the data being sorted (for example, byte sized integers as opposed to 1000 byte length strings) and the media on which the data is stored (for example, RAM as opposed to a disk drive). We should always keep in mind the impact of the time required to perform some function in the real world. A factor of 40 may have marginal impact for a real world operation that takes 1 nanosecond as opposed to 40 nanoseconds. However, an operation that takes one second as opposed to 40 seconds would be much more noticeable to a human being who might be waiting for a response from the system.

 

The following table is useful for getting a feel for the growth of some common functions.

Input Size

2

3

4

5

6

7

8

9

10

Function

Time required to process input

Log(N)

1

1.58

2

2.32

2.58

2.81

3

3.17

3.32

N

2

3

4

5

6

7

8

9

10

N*Log(N)

2

4.75

8

11.61

15.51

19.65

24

28.53

33.2

N2

4

9

16

25

36

49

64

81

100

2N

4

8

16

32

64

128

256

512

1024

N!

2

6

24

120

720

5040

40320

362880

3628800


Big O, little O and Theta

The basic definitions

Using limits to define big O, little O and Theta
One may compare the relative growth rate (order of complexity) of two functions by taking the limit of the ratio of the two functions as the independent variables approach infinity.
I.e., determine lim(f(N)/g(N)) = x as N increases.
If x=0 then the numerator is smaller than the denominator and f is o(g).
If x = infinity then the denominator is smaller than the numerator and g is o(f).
If x = a constant other than 0 then f and g have the same order of complexity. I.e., f and g are THETA of each of other.

Order of Complexity and Function Equivalence Classes

Recall from modular arithmetic that two integer, a and b are said to be in the same equivalence class with respect to modulo m iff a and b yeild the same remainder when divided by m. We denote this by writing a = b mod m.
Similarly we can define two functions, f and g, to be in the same equivalence class iff f is Theta(g).
We might refer to this as a Theta class. So, for example, x, 2*x, x/2 + 100, x + ln(x) and x - sqrt(x) are all in the same equivalence class. Following the similar concept from modular arithmetic we can write that f O= g meaning that f and g are in the same equivalence class by virtue of order of complexity..
In modular arithmetic we can define the "representative" integer of each equivalence class in mod m to be the member of the equivalence class that lies between 0 and m-1. We may thus impose a well-ordering on the equivalence classes by using the natural well-ordering of the representatives of the equivalence classes.
In a similar manner we can impose a well-ordering on the "order of complexity" equivalence classes defined above for functions. This well ordering will be defined below.
Methods of determining the order of complexity of a function

Direct comparison with another function using theorems for the sum and product of two functions and the product of a function by a constant.

The use of limits to compare functions

Bracketing a function with other functions
Let us determine the order of complexity of the function f(n) = n2*log(n)/(n+1)
Since the denominator of f(n) is a sum we cannot easily write f(n) as the sum of two simpler functions.
Consider now the functions g(n) = n2*log(n)/n and h(n) = n2*log(n)/(2*n)

Note that g(n) = 2*h(n). Therefore g and h have the same order of complexity = n*log(n).
Since the demnominator of g(n) is less than the denominator of f(n), g(n) > f(n). Also, since the denominator of f(n) is less than the denominator of h(n), f(n) > h(n).
Therefore, g(n) > f(n) > h(n).
Therefore the order of complexity of f(n) is the same as the orderof complexity of g(n) and h(n).