Algorithm Analysis

B403: Introduction to Algorithm Design and Analysis

What is an Algorithm?

  • A well defined computational procedure that takes some vales as input and produces some value, or set of values, as output.
  • An algorithm usually solves a problem. A problem is defined abstractly. For instance, sorting problem:
    • Input: A sequence of n numbers (a1, a2, ..., an)
    • Output: A permutation (a1', a2', ..., an') of the input sequence such as a1' ≤ a2' ≤ ... ≤ an'
  • An instance of a problem is a specific input needed to solve the problem. For example a list of numbers (31, 41, 5, 7, 100, 58) would be an instance of the sorting problem.
  • A correct algorithm must:
    • halt
    • produce correct output

We will revisit the issue of correctness of algorithms throughout the semester. Remember that termination of an algorithm is just as important a requirement for its correctness as that of producing correct results. There are some exceptions to this rule, but we will not mention those until we start discussing advanced topics. Until then, we will require correct algorithms to always terminate.

Why Study Algorithms?

  • Practically useful
    • Human genome project
    • Internet routing
    • Privacy in e-commerce
    • Operations management
  • Data structures
  • Techniques
  • Hard problems
  • Parallelism

Some people will argue that algorithms are what define computer science or computing. Whether or not you agree with that view, it is clear that algorithms are at the heart of computing. Understanding algorithms, their design and analysis, is not only practically useful, but also scientifically interesting. Study of algorithms also gives rise to fundamental questions about what is computing and what are the limits of computing, which is a deep and interesting subject in its own right (even discounting its numerous practical implications). Do not underestimate the value of human curiosity!

Points to Ponder

  • In analyzing algorithms we are most often concerned with their running time performance. What other measure of efficiency or performance might be relevant in real world?
  • Some times the best solution to a problem is impossible or extremely difficult to find. In such cases it may be possible to come up with an efficient algorithm to compute an approximate solution.
    • Can you imagine scenarios where only the most optimal solution will do?
    • What about scenarios when an approximation to the best is good enough?

Issues other than running time include memory footprint, energy consumption, number of I/O operations (when an algorithm involves explicit I/O), and size of the code.

Note that approximation works best when approximate solutions are acceptable. In other words, when there is not a unique solution to the problem, but there is a cost criterion that makes certain solutions more desirable than others. If you want sorted numbers then there is a unique solution. However, if you want the least cost path through a set of places then there might be multiple solutions that achieve the least cost and it makes sense to talk about an approximate solution that is a valid path, but may have higher than the least possible cost.

Insertion Sort

   Insertion-Sort (A)

   1  for j = 2 to A.length
   2    key = A[j]
   3    // Insert A[j] into the sorted sequence A[1..j-1].
   4    i = j-1
   5    while i > 0 and A[i] > key
   6       A[i+1] = A[i]
   7       i = i-1
   8    A[i+1] = key
	
    We will
  • Prove the correctness of the algorithm; and
  • Analyze its run-time complexity.

Proving Correctness: Loop Invariant Property

At the start of each iteration of the for loop of lines 1-8, the subarray A[1..j-1] consists of the elements originally in A[1..j-1], but in sorted order.

We are going to do this in three steps (note similarity with induction).

  1. Initialization: It is true prior to the first iteration of the loop (similar to base case).
  2. Maintenance: If it is true before an iteration of the loop, it remains true before the next iteration (similar to induction step).
  3. Termination: When the loop terminates, the invariant gives us a useful property that helps show that the algorithm is correct (different from induction).

Analysis Preliminaries

  • We assume a random-access machine (RAM) model:
    • Instructions are executed sequentially (no parallelism)
    • Our “instruction set” mirrors a realistic machine. Each instruction takes a constant amount of time
    • We may define macro instructions, as long as each macro instruction is of fixed size and duration
    • Word sizes are limited, so cannot store arbitrarily large number in one word
    • We do not model memory hierarchy
  • Input size depends on the problem. Commonly, it is the number of items in the input
  • Running time is the number of primitive operations or “steps”
    • We will assume that each step of our pseudo-code takes a constant amount of time

Insertion Sort

Insertion-Sort (A)
cost times
1  for j = 2 to A.length
c1 n
2    key = A[j]
c2 n−1
3    // Insert A[j] into the sorted sequence A[1..j−1].
0 n−1
4    i = j-1
c4 n−1
5    while i > 0 and A[i] > key
c5 nj=2 tj
6       A[i+1] = A[i]
c6 nj=2 (tj−1)
7       i = i-1
c7 nj=2 (tj−1)
8    A[i+1] = key
c8 n−1

Insertion Sort: Running Time

T(n) = c1n + c2(n−1) + c4(n−1) +
c5nj=2 tj + c6nj=2 (tj−1) + c7nj=2 (tj−1) +
c8(n−1)
Best case running time
T(n) = c1n + c2(n−1) + c4(n−1) + c5(n−1) + c8(n−1)
= (c1 + c2 + c4 + c5 + c8)n - (c2 + c4 + c5 + c8)

Insertion Sort: Worst Case Running Time

Worst case running time
T(n) = c1n + c2(n−1) + c4(n−1) + c5(n(n+1)2 − 1) +
c6(n(n−1)2) + c7(n(n−1)2) + c8(n−1)
= (c52 + c62 + c72) n2
+ (c1 + c2 + c4 + c62 + c62c72 + c8) n
− (c2 + c4 + c5 + c8)
= O(n2)

The kind of detailed step-by-step analysis we did for this example is often an overkill. For example, we can simplify the calculation by assuming that each constant is 1. Another simplification is to only consider the highest degree term and ignore the rest. Asymptotically, the highest degree term dominates the rest.

Divide and Conquer Approach

  • Merge Sort
    T(n) = { Θ(1) if n = 1
    2T(n/2) + Θ(n) if n > 1
  • Recursive Fibonacci
    • Exponential?
    • Multiple ways to accomplish a task
      • the most intuitive is not always the best!
    • Watch out for constant time step caveats
    • Beyond divide and conquer—dynamic programming