Week 3. Analysis of Algorithms

 

Basic Analysis

Most algorithms transform input to output objects, the running time of an algorithm typically grows with the input size. We perform analysis of algorithms to understand how the running time grows with the input size.

Average case run time is often difficult to determine so we focus on the worst case running time: it is easier to analyse and crucial for fields such as games, finance and robotics.

We could determine these values experimentally but:

  • it is necessary to implement the algorithm, which can be difficult
  • we may have a number of potential algorithms for a given task but we only want to implement one
  • results may not be indicative of the running time on other inputs not included in the experiment
  • to compare algorithms, same software / hw has to be used

Theoretical Analysis

We can use a high-level description of the algorithm (pseudo-code) instead of an implementation which takes all inputs into account. We can evaluate the speed independent of any environment.

For any given algorithm, we determine a function that characterises the worst-case scenario running time of the algorithm for the input size .

Pseudo-code

Pseudo-code is a high-level description of an algorithm, it is less detailed than a program but more structured than just English. It is preferred for describing algorithms and hides program design issues.

Link to original

Random Access Machine Model

Random Access Machine Model

The Random Access Machine Model consists of:

  • a CPU
  • a memory; potentially unbounded bank of memory cells
  • unit time; each CPU operation takes unit time; accessing any cell in memory takes unit time This is an approximation of real computers but mostly sufficient for predicting real running times.
Link to original

Primitive Operations

Primitive Operations

We can model primitive operations (assigning value to variable, comparing two values, evaluating expression, indexing into an array, following object reference, calling a method, and returning from a method) to take a constant amount of time.

Link to original

We can determine the maximum number of primitive operations by inspecting the pseudo-code of the algorithm:

def arrayMax(A, n):
	currentMax = A[0]         # 2
	for i = 1 to n - 1:       # 2n + 1
		if A[i] > currentMax: # 2(n - 1)
			currentMax = A[i] # 2(n - 1)
		{ i += 1 }            # 2(n - 1)
	return currentMax         # 1
                      # Total = 8n - 2
Estimating Running Time

Given the algorithm arrayMax executes primitive operations in worst case. Let us define as the time taken by the fastest primitive operation, and be the time taken by the slowest primitive operation. Given is the worst-case time:

  • is the lower bound (fastest worst-case running time)
  • is the upper bound (slowest worst-case running time) Hence we can also define as bounded by two linear functions of :

Consider that , then the running time can be found: .

Growth Rate of Running Time

Changing the hardware / software environment affects by a constant factor but does not alter the growth rate of . The linear growth rate of the running time is an intrinsic property of the algorithm and is independent of hardware, implementation and computing environment.

We primarily consider seven functions:

  • Constant:
  • Logarithmic:
  • Linear:
  • n Logarithmic:
  • Quadratic:
  • Cubic:
  • Exponential:

The growth rate is not affected by:

  • constant factors:
  • lower-order terms:

Big-O Notation

Big-O Notation

The Big-O notation gives an upper bound on the growth rate of a function. Given functions and , we say that is , if there are positive constants and such that for .

In general, if is a polynomial of degree then is .

  • Drop lower-order terms.
  • Drop constant factor in highest order term.

Examples:

  • is
  • is
  • is
  • is
Link to original

The statement means that the growth rate of is not greater than the growth rate of . We can use Big-O notation to rank functions according to their growth rate.

is is
grows moreYesNo
grows moreNoYes
Same growthYesYes

Asymptotic Algorithm Analysis

Asymptotic Algorithm Analysis of an algorithm determines the running time in Big-O notation, to perform analysis:

  • we find the worst-case number of primitive operations executed as a function of the input size
  • we don’t need this function exactly, we just want to express it using Big-O

If we determine that the algorithm arrayMax executes at most primitive operations then we say that the algorithm arrayMax “runs in time”. Since constant factors and lower-order terms are eventually dropped, we can disregard them when counting primitive operations.

Link to original

Example: Computing Prefix Averages

We can demonstrate Asymptotic Algorithm Analysis with two algorithms for prefix averages.

The -th prefix average of an array is the average of the first elements of : .

For a sample array , we see the data in to look like:

Computing the array of prefix averages of another array has applications in financial analysis.

Quadratic Implementation

The following algorithm computes prefix averages in quadratic time by directly applying the definition:

def prefixAverages(X, n):
	Input array X of n integers
	Output array A of prefix averages of X
	A = int[n]
	for i = 0 to n - 1:
		s = X[0]
		for j = 1 to i:
			s = s + X[j]
		A[i] = s / (i + 1)
	return A

The running time of prefixAverages is hence we have . The sum of the first integers is , hence we can say that prefixAverages runs in time.

Linear Implementation

The following algorithm computes prefix averages in linear time by keeping a running sum.

def prefixAverages(X, n):
	Input array X of n integers
	Output array A of prefix averages of X
	A = new array of n integers
	s = 0
	for i = 0 to n - 1:
		s = s + X[i]
		A[i] = s / (i + 1)
	return A

Big-O and Relatives

There are three different notations we need to be aware of:

  • Big-O where is if asymptotically .
  • Big-Omega where is if asymptotically .

    Big-Omega

    Big-Omega defines if there is a constant and an integer constant such that such that for .

    Link to original
  • Big-Theta where is if asymptotically .

    Big-Theta

    Big-Theta defines if there is a constant and an integer constant such that such that for .

    Link to original

Examples: