Big O, Theta, and Omega Notation - 8/21

Big O

  • Some function c*g(n) that is an upper bound to the described function f(n)
  • Limit method for determining Big O
    • If lim (f(n))/(g(n)) exists, then f(n) = O(g(n)) if and only if that limit is < some constant c
    • If g(n) is “heavier” then the ratio will tend towards a constant, which means there is some constant that can be applied to g(n) to create an upper bound
    • It is possible that f(n) is still limited by O(g(n)) but the limit doesn’t exist for example (2+(-1)^n)g(n)

Little o

  • a stricter upper bound of Big O. That not only is there some constant C that can upper bound f(n), but ANY constant * g(n) can be used as an upper bound for f(n)
  • f(n) = o(g(n)) if and only if lim f(n)/g(n) == 0

Big Omega ( Ω )

  • Ωg(n) there exists a constant c > 0 where c*g(n) f(n) for all n > n0
  • Used to indicate the minimum runtime of the algorithm
    • lower bound for the growth rate of f(n)
  • limit method can also be used for Big Omega Ω
    • similar to Big O, but the limit is greater than some constant C. lim f(n)/g(n) > C

Little Omega ( ω )

  • similar to Little o in its application where we have a more restrictive lower bound for g(n), that for any constant c*g(n) holds as a lower bound for f(n)
  • f(n) = ωg(n) if and only if lim f(n)/ g(n) == ∞
  • (n^2) = ω(n) because for any constant c, after some n0, n^2 will be lower bounded by n
    • lim n^2 / n == ∞

Big Theta ( Θ )

  • If there is some constant that both f(n) = O(g(n)) AND f(n) = Ω(g(n)) then it can be said that f(n) = Θ(g(n)) which indicates the same growth rate

Reflexivity / Symmetric / Transitivity / Transpose

  • Reflexivity
    • A function is always bounded by the upper and lower by itself
    • Holds for all functions, O, o, Θ, Ω, and ω
  • Symmetric
    • Holds for Θ notation, where a function that holds for Θ(g(n)), it is true that g(n) = Θ(f(n))
  • Transitivity
    • For all functions, if f(n) = O(g(n)) and g(n) = O(h(n)) f(n) = O(h(n))
  • Transpose
    • A big O, or little o relationship between f(n) and g(n) necessarily implies a Big Omega or little omega relationship in the opposite direction respectively

Function Iteration

  • functionally acts as a function of function, but rather than being F o g or F o h, we are recursively applying F o f.
  • Written as f ^ i where i is the iteration
    • Definitionally f ^ 1 is just the normal definition of the functions
    • f ^ 2 f(f^1) or in other syntax F o f(x)

Iterated Logarithm

  • One of these preset functions is logarithm with its own notation “star notation”
    • log * n = min{ i >= 0 : log^i n 1}
    • log(log(log(log (log (2^65536))) = 5
      • log * 2^65536 = 5

Divide-and Conquer / Recurrence Relations

  1. Generally we want to divide the problem into many smaller problems of the same sort.
    • Splitting n into two functions of size n/2
  2. Conquer each subproblem recursively
  3. Combine the solutions of subproblems to solve the origianl problem

Merge Sort

  1. Split the array into two halves ( left and right)
  2. if the array size > 2 repeat step 1
  3. else sort the two elements, return
  4. recursively merge the sorted halves
  • This sorts an array in O(n log n) which is the best possible time for a random input sorting algorithm

Techniques for Solving Recurrences

  • Recurrence Relations generally follow the form
    • T(n) = aT(n/b) + f(n)
    • n/b represents the size of the sub problem
    • a is the number of times each size is called in each sub problem
    • f(n) is the cost for the non recursive part
    • Merge sort would have the relation T(n) = 2T(n/b) + f(n)

Substitution Method

  1. guess the form of the solution
  2. Prove the guess using induction
  • We give some hypothesis, some guess, that the function T will be bounded by Theta( some function)
  • Given T(n) plug your function in for T(n)
    • T(n) = 2T(n/2) + c becomes T(n) = 2(n log n/2) + c

Recursion Tree

  1. Draw the size and number of nodes labeled in reference to n
    • The first layer has one node n, layer two has two nodes of size n/2 etc.
    • the root node is the original problem
    • The leafs are the base cases. For merge sort that constant would be when the node is of size 1
  2. each layer has a time complexity of the sum of each node
  3. Solve for the height of the tree
  4. The total work = height of the tree * work per level

Master Method

Master Method

  • If a < b then we know that the function is sublinear
  • if b < a then the function is some function greater than n^1 ( aka linear)
  • Works in the form T(n) = aT(n / b) + O(n^d)
  • There are three cases for a valid use case
    • For all three use cases you need to know log(base b) a [denoted as loga from here on out]and its relation to d
      1. d > loga
        • This means the work dominates, thus T(n) = O(n^d)
      2. d ~ loga
        • If d and loga are of the same magnitude (constant factor)
        • T(n) = O(n^d log n)
        • Ideal case
      3. d < loga
        • This means the recursive work is dominating
        • T(n) - O(n^loga)
  • Steps to solve
    1. pull out a,b,d,
    2. find loga
    3. compare d and loga
    4. choose correct case, and get runtime

Maximum Subarray

  • Given an array find a non-empty contiguous subarray whose values have the largest sum
    • Example A = [-2,1,-1,4,-1,2,1,-5,4]
    • Maximum subarray B = [4,-1,2,1]
  • Brute-Force Approach
    • With a nested for loop try every possible subarray keeping track of the largest sum from those subarrays. obviously this runs in O(n^2)
  • Divide and Conquer Solution
    1. Divide the array into two parts A[1:mid] and A[mid:A.length]
    2. The maximum subarray is either
      1. Entirely in first half
      2. Entirely in second half
      3. Or crossing the midpoint
    • The first two cases we can handle recursively in n log n
    • For the 3rd case we need to use a find-max-crossing-subarray function
      • We have two sums one fore the left and one for the right, initialized to -inf
      • starting from mid, we go left and right respectively, updating the current and max sum for each side
      • The max_left and max_right is typically the index because the actual sum doesn’t matter just that the subarray is the maximum
      • O(n)
    • Compare the three test cases to find the total maximi

Quicksort Algorithm

  • Has a worst case of O(n^2), but an amortized runtime O(n log n)
    • The reason you would use this over merge sort which is guaranteed n log n is because quicksort is an in place algorithm.
    • Its possible that you choose a pivot and get unlucky and all the elements end up on one side, causing the time to approach O(n)
  • Choosing the Pivot
    • We can choose an arbitrary position such as the first or last position
    • We can choose a random index
    • This has an impact on the runtime. a good pivot (n log n) run time, and a poor choice (n^2)
  • Partition function
    • both i and j start at 0 and we iterate from 0 r-1 if the element is less than r, the i index progresses, otherwise the j index progresses.
    • We swap i and j if the element is greater than the pivot.
  • Order of operations
    1. split the array into two partitions using some pivot value
    2. recursively call quicksort on each partition, to create its own partition
    3. In the partition function, we put all elements less than the pivot in the left subarray, and all greater than the pivot in the right subarray
      1. partition garaunteed in the right position at the end ( all to the left are less, and all to the right are higher)
  • Reccurence Relation
    • T(n) = 2T(n/2) + n
    • Rule 1 of master relation
      • f(n) > n^(logba)
    • T(n) = Θ(n log n)

Selection Problem

  • Given some unsorted array, find the smallest element in A
  • We could first sort the array, with merge sort or quick sort, and then just index A[1], which would solve our problem in O(n log n)

Quickselect

  • A recursive algorithm that gives us the smallest element, and only that one element in expected O (n) which is much faster than the naive approach
  • Using the same partition function from above, split and check the lowest with subarray
  • Can use the Median of Medians to find a good pivot
    • Guarantees > 60% of array is in correct subarray relative to the pivot
    • break array into groups of 5 (A[0:4], A[5:9], A[10:14])
      • if the last subarray has < 5 elements because n % 5 != 0, then we just choose one element arbitrarily
    • find the median of each subarray
    • Find the median value of the medians calculated in the previous step. This is your pivot

Decision Tree for Sorting

A decision tree is a binary tree that represents the sequence of comparisons

  • Each node is labeled with a comparison i:j
  • each leaf node represents a permutation of the array

Dynamic Programming

Subsequences

  • Increasing Subsequence
    • An increasing subsequence is a subsequence Z that stricly increases from element to element
      • Ex. Z = [1,2,4,6]
      • Anti-Example Z =[1,2,4,3]

Longest Increasing Subsequence

  • Given some sequence X, what is the longest increasing subsequence?
    • We are concerned with the length of the sequence, not the value or index.
  • If we are to find the LIS ending at X i=6, then we know, all the elements we choose in the interval must be less than X[6].
    • using these chosen indeces, we can recursively count the LIS ending in each index chosen. The max of these subproblems will be the LIS of the total subsequence - 1

Longest Common Subsequence

  • Prefix

Dynamic Programming Examples

  • Chain Matrix Multiplication
    • When multiplying a chain of matrices, different order of operations in terms of the associativity of the multiplication will have vastly different run times.
    • That is, in terms of the number of calculations needed, and total runtime, A1(A2*A3) != (A1* A2) A3
    • We then given some number of Matrices need to find the ordering that results in the least number of multiplications