Intro to Algo Lecture 2-1

Sorting
Insertion sort \(O\)(n2)
Merge sort \(O\)(n log n) by divide and conquer

Analyze complexity of recursions
By expansion: the recursion tree method
By induction: the substitution method

In-place sort - sorted item occupy the same storage as the original one
Out-of-space sort - sorting algo used extra space to do sorting

INSERTIONSORT(A)
START
  FOR i = 1 to n
    WHILE A[i] < A [i -1]
        swap A[i] with A[i-1]
        i -= 1
    ENDWHILE
  ENDFOR
END

SWAP(A, i)
START
  temp <-- A[i]
  A[i] <-- A[i-1]
  A[i-1] <-- temp
END

Worst-case running time \(T\)(n) on an input of size n

\(T\)(n) = \(\frac{n(n-1)}{2}\) = \(\Theta\)(n2)

Divide and conquer solution
- Divide input into parts (smaller problems)
- Conquer (solve) each part recursively
- Combine results to obtain solution of original

\(T\)(n) = divide time [\(\Theta\)(n)]
T(n1) + T(n2) + … + T(nk) [2\(T\)(n/2)]
combine time [\(\Theta\)(n)]

\(T\)(n) = 2\(T\)(n/2) + \(\Theta\)(n)

MergeSort(A)
START
IF n = 1 THEN
  done -> return
ENDIF
RECURSIVE_SORT A[:n/2] --> L
RECURSIVE_SORT A[n/2:] --> R
merge L & R --> output A'
END
MERGESORT(a)
START
  IF n == 1 THEN
    RETURN a

   l1 <-- a[0] ... a[n/2]
   l2 <-- a[n/2+1] ... a[n]

   l1 <-- call: MERGESORT(l1)
   l2 <-- call: MERGESORT(l2)

   RETURN MERGE(l1, l2)
END

MERGE(a, b)
START
  c <-- []
  WHILE a and b have elements
    IF a[0] > b[0] THEN
      add b[0] to the end of c
      remove b[0] from b
    ELSE
      add a[0] to the end of c
      remove a[0] from a
    ENDIF
  ENDWHILE
     
  WHILE a has elements
    add a[0] to the end of c
    remove a[0] from a
  ENDWHILE
  
  WHILE b has elements
    add b[0] to the end of c
    remove b[0] from b
  ENDWHILE
  
  RETURN c
END

Intro to Algo Lecture 2-2

The recursion tree = sum of cost of nodes

\(T\)(n) = 2\(T\)(n/2) + cn
Sum of each level and then sum all levels \(T\)(n) = n\(T\)(1) + cn log n \(\Theta\)(nlogn)

\(T\)(n) = \(T\)(n/2) + cn
\(T\)(n) = cn + cn/2 + cn/4 + …. + 1 = cn (1 + ½ + ¼ + …. + 1/2logn) = cn (2) = Θ(n)

Master Theorem

Sum of each value in each level is the same, Total value = \(\Theta\)(no. of levels * sum of each lvl)
Each level’s value is decreasing, Total value = \(\Theta\)(root node’s value)
Each level’s value is increasing, Total value = \(\Theta\)(no. of leaves)

\(T\)(n) = a \(T\)(n / b) + f(n)

No. of lvl = logb(n) = \(\Theta\)(log n)
No. of leaves = alogb(n) = nlogb(a)

  1. value of each lvl is geometrically increasing down the tree

    if f(n) = \(O\)(L1-\(\epsilon\)) = \(O\)(nlogb(a)-\(\epsilon\))
    \(T\)(n) = \(\Theta\)(L) = \(\Theta\)(nlogb(a))
    Note: dominant no. of leaves

    log(n) –> log(n/2) –> log(n/4) –> … –> log(n / 2k) –> c

    No. of leaves increasing down the tree (dominant)
    f(n) = \(O\)(L1-\(\epsilon\)) = \(O\)(nlogb(a)-\(\epsilon\))
    T(n) = \(\Theta\)(nlogb(a)) = \(\Theta\)(n)

  2. value of each lvl are equal Height \(\times\) Leaves

    f(n) = \(\Theta\)(L) = \(\Theta\)(nlogb(a)) T(n) = \(\Theta\)(L log n) = \(\Theta\)(n(logb(a) log(n)) = \(\Theta\)(n log n), a = b = 1

    n –> n/2 –> n/4 –> … –> n/2k –> c
    T(n) = \(\Theta\)(L log n) = \(\Theta\)(nlogb(a) log n)

  3. value of each is lvl is geometrically decreasing down the tree

    f(n) = \(\Omega\)(L1+\(\epsilon\)) = \(\Omega\)(n(logb(a)+\(\epsilon\))
    \(T\)(n) = \(\Theta\)(f(n))
    Note: dominant node

    n2 –> (n/2)2 –> (n/4)2 –> … –> (n/2k)2 –> c
    n2 is significantly bigger than n log n

Exercise 1 (Case 1)

\(T\)(n) = 9\(T\)(n/3) + n

a = 9, b = 3, f(n) = n
height = log3(n); leaves = 9^log3(n) = nlog3(9) = \(\Theta\)(n2)
Note: height = logb(n) (bheight = n), #leaves = alogb(n) = nlogb(a)

Since f(n) = \(O\)(nlog3(9-\(\epsilon\))) where \(\epsilon\) = 1 \(T\)(n) = \(\Theta\)(#leaves) = \(\Theta\)(n2)

Exercise 2 (Case 2)

\(T\)(n) = \(T\)(2n/3) + \(\Theta\)(1)

a = 1, b = 3/2, f(n) = 1
height = log(3/2)(n); leaves = log3/2(n) = nlog3/2(1) = n0 = \(\Theta\)(1)

f(n) = \(\Theta\)(nlog(3/2)(1))

\(T\)(n) = \(\Theta\)(nlog(3/2)(1) log n = \(\Theta\)(log n)

Exercise 3 (Case 3)

\(T\)(n) = 3\(T\)(n/4) + n log n

a = 3, b = 4, f(n) = n log n
height = log4(n); #leaves = 3log4(nlogn) = nlog4(3) \(\approx\) n0.793

f(n) = \(\Omega\)(nlog4(3+\(\epsilon\))) where \(\epsilon\) 0.2
AND af(n/b) = 3f(n/4) = 3/4n log(n/4) <= cn log n, for c = 3/4 (regularity condition)
check if value is decreasing, if c < 1 and cn log n dominates 3/4n log(n/4)
\(T\)(n) = \(\Theta\)(f(n)) = \(\Theta\)(n log n)

Exercise 4 (Case 2)

\(T\)(n) = 4\(T\)(n/2) + n2
a = 4, b = 2, f(n) = n2
height = log2(n), #leaves = 4log2(n) = nlog2(4) = n2

f(n) = \(\Theta\)(n2)
\(T\)(n) = \(\Theta\)(n2 log n)

Exercise 5 (Case 3)

\(T\)(n) = 7\(T\)(n/3) + \(\Theta\)(n2)
a = 7, b = 3, f(n) = n2
height = log3(n), #leaves = 7log3(n) = nlog3(7)

f(n) = \(\Omega\)(nlog3(7+\(\epsilon\))), where \(\epsilon\) \(\approx\) 0.229

Since af(n/b) = 7f(n/3) = 7(n/3)2 = 7/9n2 <= cn2, for c = 7/9 (regularity condition)

\(T\)(n) = \(\Theta\)(n2)

Exercise 6 (Unable to resolve using Master Theorem, use recursive tree)

\(T\)(n) = \(T\)(n - 1) + 2
2 –> 2 –> … –> 2
n –> n-1 –> 1 2(n); \(T\)(n) = \(\Theta\)(n)

Intro to Algo Cohort 2-3

Peak Finding Problem

Neighbors < Peak element

10, 13, 5, 8, 3, 2, 1

PEAKFINDER(A)
START
  IF A.length == 0 THEN //If list is 0, there is no peak
    return NIL
  ENDIF
    
  IF A.length == 1 THEN // If list has been reduced to 1 element, it's a peak
    return A
  ENDIF
    
  mid = A.length / 2
  
  left = mid - 1
  right = mid + 1
  
  IF A[left] <= A[mid] >= A[right] THEN
    RETURN A[mid]
  ELSE IF A[mid] < A[left]
    RETURN PEAKFINDER(A[:mid])
  ELSE IF A[mid] < A[right]
    RETURN PEAKFINDER(A[mid+1:])
  ENDIF
END

An element A[i] is a peak if it is not smaller than all its neighbor(s)

IF i != 1 THEN
  n <-- A[1] >= A[i - 1] and A[i] >= A[i + 1]
ELSE IF i = 1
  A[1] >= A[2]
ELSE IF i = n
  A[n] >= A[n-1]
ENDIF

13 & 8 are peaks

Algo 1
Scan the array fr left to right
Compare A[i] with its neighbors
Exit when found a peak

1, 2, 4, 8, 9, 12, 21

Complexity:
Might need to scan all elements, so \(T\)(n) = \(\Theta\)(n)

Algo 2
Consider the middle element of the array & compare with neighbors

IF A[n/2-1] > A[n/2] THEN
  search for a peak among A[1]... A[n/2  - 1]
ELSE IF A[n/2] < A[n/2 + 1]
  search for a peak among A[n/2 + 1]...A[n]
ELSE
  A[n/2] is a peak! //since A[n/2 -1] <= A[n/2] and A[n/2] >= A[n/2 + 1]
ENDIF

Complexity
\(T\)(n) = \(T\)(n/2) + \(O\)(1) //Time for comparing A[n/2] with 2 neighbors
\(T\)(n) <= \(T\)(n/2) + c <= (\(T\)(n/22) + c) + c <= … <= \(T\)(n/2log2(n)) + c + c + … + c = 1 + log2(n) = \(O\)(log n)

Time to find peak in array of length n, O(1) time to find peak, \(T\)(n/2) cut array in half

Divide and Conquer
Very powerful design tool:
Divide input into multiple disjoint parts
Conquer each of the parts separately (using recursive call)

Peak finding: 2D
Consider a square 2D array A[1…n, 1…n]:
An element A[i] is a 2D peak if it is not smaller than its (at most 4 neighbors.
Problem: find any 2D peak

Algo 1: brute-force method (i.e. search all squares)
Complexity = \(\Theta\)(n2)

Algo 2:
a) For each col j, find global maximum B[j]: complexity = \(\Theta\)(n)
b) Apply 1D peak finder to find a peak of B[1…n] (say B[j]): complexity = \(\Theta\)(n2 + log n), log n to
find the peak

Complexity = \(\Theta\)(n2 _ log n) = \(\Theta\)(n2)

Algo 3:

Pick middle col j = n/2<br>
Find global max  on col j, [i, j]<br>
Compare A[i, n/2] to A[i, n/2 - 1]  A[i, n/2 + 1]<br>

IF A[i, n/2] < A[i, n/2-1] THEN
  pick left col j=[1...n/2]
ELSE IF A[i, n/2] < A[i, n/2+1]
  pick right col j = [n/2...n]
ELSE IF A[i, n/2] >= A[i, n/2 -1] AND A[i, n/2] >= A[i, n/2+1]
    A[i, j] is 2D peak
PEAKFINDER(A, rows, cols, mid)
START
  max <-- 0
  max, max_index <-- call: FINDMAX(A, rows, mid, max)
  
  IF mid == 0 OR mid == cols - 1 THEN // If on the first or last col, max is a peak
    RETURN max
  ENDIF
  
  IF max < A[max_index][mid - 1] // If max is less than left
    RETURN FINDMAX(A, rows, cols, mid - (mid / 2))
  ELSE IF max < A[max_index][mid + 1] // If max is less than right
    RETURN FINDMAX(A, rows, cols, mid + (mid / 2))
  ELSE IF max >= A[max_index][mid - 1] AND max >= A[max_index][mid + 1] // If max is more than left and right
    RETURN max
  ENDIF
STOP

FINDMAX(A, rows, mid, max)
START
  max_index <-- 0
  FOR i = 0 to row
    IF max < A[i][j] THEN // Find global max  on col mid, [i, mid]
      max <-- A[i][mid]
      max_index <-- i
    ENDIF
  ENDFOR
END

Complexity = \(T\)(m/2) + \(\Theta\)(n) cost of finding the maximum in a col
\(T\)(n) = \(T\)(n/2) + \(\Theta\)(n) = \(T\)(n/4) + 2 * \(\Theta\)(n) = \(T\)(n/16) + 3 * \(\Theta\)(n)
= log(n) \(\Theta\)(n) = \(\Theta\)(n log n)

\(\Theta\)(n log m)

  1. \(T\)(n) = 3\(T\)(n/2) + n2
    a = 3, b = 2, f(n) = n2
    height = log2(n), #leaves = 3log2(n) = nlog3

    Compare nlog3 and n2
    Complexity = \(\Theta\)(n2)

  2. \(T\)(n) = 9\(T\)(n/3) + n2
    a = 9, b = 3, f(n) = n2
    height = log3(n), #leaves = 9log3(n) = nlog3(9) = n2

    Complexity = \(\Theta\)(n2 log n)

  3. \(T\)(n) = \(T\)(n/2) + 2n
    a = 1, b = 2, f(n) = 2n
    height = log2(n), #leaves = n^log2(1) = 1

    Compare 2n and 1
    Complexity = \(\Theta\)(2n) < 2n + 2n = 2 * 2n = \(\Theta\)(2n)

    \(T\)(n) = 2n + 2(n/2) + … + 2 + 1
    2k > 2(k-1) + 2(k-2) + … + 2 + 1
    (2k) - 1