Common Functions Used in Analysis
Common Functions Used in Analysis
Common Functions Used in Analysis
The value b is known as the base of the logarithm. Computing the logarithm function for any integer n is not always easy, but we can
easily compute the smallest integer greater than or equal to logbn, for this number is equal to the number of times we can divide n by b
until we get a number less than or equal to 1. For example, log327 is 3, since 27/3/3/3 = 1. Likewise, log212 = 4, since 12/2/2/2/2 =
0.75 <= 1.
The base-two approximating arises in the algorithm analysis, since a common operation in many algorithms is to repeatedly divide an
input in half. In fact, the most common base for the logarithm in computer science is 2. We typically leave it off when it is 2.
Logarithm function gets slightly slower as n grows. Whenever n doubles, the running time increases by a constant.
Examples: binary search.
The Linear Function f(n) = n
Another simple yet important function. Given an input value n, the linear function f assigns the value n itself.
This function arises in an algorithm analysis any time we do a single basic operation for each of n elements. For example, comparing
a number x to each element of an array of size n will require n comparisons. The linear function also represents the best running time
we hope to achieve for any algorithm that processes a collection of n inputs.
Whenever n doubles, so does the running time.
Example: print out the elements of an array of size n.
The N-Log-N Function f(n) = nlogn
This function grows a little faster than the linear function and a lot slower than the quadratic function (n2). If we can improve the
running time of solving some problem from quadratic to N-Log-N, we will have an algorithm that runs much faster in general. It
scales to a huge problem, since whenever n doubles, the running time more than doubles.
Let's draw the growth rates for the above functions and take a look at the following table.
The big-Oh notation allows us to say that a function f(n) is less than or equal to another function g(n) up to a constant factor and in
the asymptotic sense as n grows towards infinity. If f(n) is of the form of An + B, where A and B are constants. It's called a linear
function, and it is O(n).
The big-Oh notation gives an upper bound on the growth rate of a function. The statement "f(n) is O(g(n))" means that the growth
rate of f(n) is no more than the growth rate of g(n).
Characterizing Running Times using the Big-Oh Notation
The big-Oh notation is used widely to characterize running times and space bounds in terms of some parameter n, which varies from
problem to problem, but is always defined as a chosen measure of the size of the problem. For example, if we are interested in finding
a specific element in an array of integers, we should let n denote the number of elements of the array. Using the big-Oh notation, we
can write the following mathematically precise statement on the running time of a sequential search algorithm for any computer.
Proposition: The sequential search algorithm, for searching a specific element in an array of n integers, runs in O(n) time.
Justification: The number of primitive operations executed by the algorithm findElement in each iteration is a constant. Hence, since
each primitive operation runs in constant time, we can say that the running time of the algorithm findElement on an input of size n is
at most a constant times n, that is, we may conclude that the running time of the algorithm findElement is O(n).
Some Properties of the Big-Oh Notation
The big-Oh notation allows us to ignore constant factors and lower order terms and focus on the main components of a function that
affect its growth the most.
Example: 5n4 + 3n3 + 2n2 + 4n + 1 is O(n4).
Justification: 5n4 + 3n3 + 2n2 + 4n + 1 <= (5 + 3 + 2 + 4 + 1)n4 = cn4 , for c = 15 and n0 = 1.
Proposition: If f(n) is a polynomial of degree d, that is, f(n) = a0 + a1n + a2n2 + ... + adnd, and ad > 0, then f(n) is O(nd).
Justification: Note that, for n >= 1, we have 1 <= n <= n2 <= ... <= nd; hence f(n) = a0 + a1n + a2n2 + ... + adnd <= (a0 + a1 +... + ad)
nd; therefore, we can show that f(n) is O(nd) by defining c = a0 + a1 +... + ad and n0 = 1.
The highest degree term in a polynomial is the term that determines the asymptotic growth rate of that polynomial.
General rules: Characterizing Functions in Simplest Terms
In general we should use the big-Oh notation to characterize a function as closely as possible. For example, while it is true that f(n) =
4n3 + 3n2 is O(n5) or even O(n4), it is more accurate to say that f(n) is O(n3).
It is also considered a poor taste to include constant factors and lower order terms in the big-Oh notation. For example, it is
unfashionable to say that the function 2n3 is O(4n3 + 8nlogn), although it is completely correct. We should strive to describe the
function in the big-Oh in simplest terms.
Rules of using big-Oh:
If f(n) is a polynomial of degree d, then f(n) is O(nd). We can drop the lower order terms and constant factors.
Use the smallest/closest possible class of functions, for example, "2n is O(n)" instead of "2n is O(n2)"
Use the simplest expression of the class, for example, "3n + 5 is O(n)" instead of "3n+5 is O(3n)"
Generally speaking, any algorithm running in O(nlogn) time (with a reasonable constant factor) should be considered efficient. Even
O(n2) may be fast when n is small. But O(2n) should almost never be considered efficient.
If we must draw a line between efficient and inefficient algorithms, it is natural to make this distinction be that between those
algorithms running in polynomial time and those running in exponential time. Again, be reasonable here. O(n100) is not efficient at all.
Exercises
Show that if d(n) is O(f(n)), then ad(n) is O(f(n)), for any constant a > 0.
Big-Omega
The big-Oh notation provides an asymptotic way of saying that a function is less than or equal to another function. This big-Omega
notation provides an asymptotic way of saying that a function grows at a rate that is greater than or equal to that of another.
Let f(n) and g(n) be functions mapping nonnegative integers to real numbers. We say that f(n) is (g(n)) (pronounced "f(n) is bigOmega of g(n)") if there is a real constant c > 0 and an integer constant n0 >= 1 such that f(n) >= cg(n), for n >= n0.
2T(n/2) +
2[2T(n/4)
4T(n/4) +
...
2k T(n/2k)
n
+ n/2] + n
2n
+ kn
We know that T(1) = 1 and this is a way to end the derivation above. In particular we want T(1) to appear on
the right hand side of the = sign. This means we want:
n/2k = 1 OR n = 2k OR log2n = k
Continuing with the previous derivation we get the following since k = log2n:
=
=
=
=
2kT(n/2k) + kn
2log2n T(1) + (log2n)n
n + n [ remember that T(1) = 1 ]
O(n log n)
So we've solved the recurrence relation and its solution is what we "knew" it would be. To make this a formal
proof you would need to use induction to show that O(n log n) is the solution to the given recurrence relation,
but the "plug and chug" method shown above shows how to derive the solution --- the subsequent verification
that this is the solution is something that can be left to a more advanced algorithms class.
Algorithm
Binary Search
T(n) = T(n-1) + O(1) Sequential Search
T(n) = T(n/2) + O(1)
T(n) = 2 T(n/2) +
O(1)
T(n) = T(n-1) + O(n)
T(n) = 2 T(n/2) +
O(n)
Big-Oh
Solution
O(log n)
O(n)
tree traversal
O(n)
O(n2)
O(n log n)