Shiv After Dark

[NOTES] CS 6515: Intro to Graduate Algorithms Recap

Update: Finally got an A in CS-6515.

You'll know soon enough what this class is about. I'll instead tell y'all what it's like to take this class:

  1. It's referred to as the "final-boss" of the MS CS degree at GaTech.
  2. To graduate, you need a minimum grade of B.
  3. A lot of students attempt this class twice (or more) to get a B.
  4. This class often leads to severe burnout and also many students change their specialization to completely avoid this class.
  5. This was the grade distribution in the summer (45.8% class basically either failed or dropped the class): summergrade
I

I have started writing this post on 5th December, 2024; five days before the CS-6515 final exam. Here are some reasons why I shouldn't be taking this exam:

  1. I already have a B in this class so effectively the final is optional.
  2. I start a new job soon after this exam. I could instead spend the time relaxing and doing fun things.
  3. No one will care about my final grade.
  4. Several of my classmates and TAs have advised against taking this exam because I need 85+ in the final exam to get an A. This is an especially hard target because the final exam covers the entire syllabus and hence, is significantly harder.
  5. I'm running on fumes after a year of studying. It's mentally arduous and I don't think I can study even for a minute now.

I'm left with few convincing reasons to take this exam. After an hour of contemplation these are the reasons why I WILL take the exam:

  1. I don't think I can score an 85+ in the finals. Attempting the final is futile. But I feel it is my duty to give my best and it would bring great honour to the legacy of Chatrapati Shivaji Maharaj, if I uphold my duties.
  2. I'm severely burnt out and I cannot continue studying. Or maybe this is a lie. Only one way to find out.
II

Note: These are meant to be rough/short-hand notes that will help me retain information that I've already studied in extreme depth previously. It's difficult for me to study right now because there isn't enough mental stimulation/challenge to enter a flow-state. Writing notes in LaTex and actively trying to summarize the syllabus slightly increases the challenge and only maximise the flow-state. To the general audience, these will not be very useful.

1. Notations

  1. Big-O notation: f(n)=O(g(n)). Important: f(n)g(n)
  2. small-o notation: f(n)=o(g(n)). Important: f(n)<g(n)
  3. Big-Omega notation: f(n)=Ω(g(n)). Important: f(n)g(n)
  4. small-omega notation: f(n)=ω(g(n)). Important: f(n)>g(n)

2. Proofs by Induction

This is not gonna be directly tested but there are standard patterns in which proofs by induction that you can simply use in your exams.

  1. Base Case: Easiest part where you argue why algorithm is right for input size 1 or 0 (or whatever is base level input). You WILL get partial marks here (so don't fumble this)
  2. Hypothesis: Claim that the algo holds true for inputs up to size n.
  3. Induction Step:

3. Master's Theorem

I just need to remember some basic results to solve short questions. This is mostly used to quickly figure out runtimes of some recursive functions.

T(n)=a.T(n\b)+O(nc)
  1. When we unroll a recursion tree then on ith level of the tree, the input size is (n/bi).
  2. When we unroll a recursion tree then on ith level of the tree, the number of nodes/vertices are ai.
  3. When we unroll a recursion tree then on ith level of the tree, the total time spent is:
O((n/bi)c.ai)
  1. Across all the layers and nodes the total time spent is:
O(nci=0logb(n)(a/bc)i)

There are three cases for Master's Theorem:

  1. a=bc: O(nclog(n))
  2. a<bc: O(nc)
  3. a>bc: O(nlogb(a))

4. Polynomial Multiplication and FFT

  1. Time Complexity: O(d.log(d)) where d is the degree of the polynomial
  2. Product Representation:
(i=0dxifi).(i=0dxigi)=(i=02d(xi.k=0igk.fik))
  1. Where k=0i and ik=i0
  2. Data structure is roughly this:
  1. Imp Observation:
  1. Some FFT Tricks:

5. DFS, BFS and Topological Order

  1. DFS/BFS Runtime: O(|E|)
  2. Topological Order: Order of vertices so that all edges go "left to right". Sorting by post-order in descending direction gives a valid topological order.
  3. Edge Types: (note that you will have to refer to the algorithm at the end of this section)
  1. The algorithm roughly looks like this:
preorder = [0] * n
postorder = [0] * n
precounter = 0
postcounter = 0
visited = {}
def DFS(v):
    nonlocal preorder, postorder, precounter, postcounter
    visited[v] = True
    preorder[v] = precounter
    precounter += 1
    for neighbour u of v:
        if not visited[u]:
            DFS(u) # Tree Edge
        elif postorder[u] == 0:
            # (v, u) is a backedge
        elif preorder[u] > preorder[v]:
            # (v,u) is forward edge
        else:
            # (v,u) is cross edge
     postorder[v] = postordercounter
     postordercounter += 1
  1. DFS/BFS: STACK/QUEUE.

6. Search Algorithms

Note that these results can be directly used in exams.

  1. Theorem: Dijkstra's Algorithm finds the shortest path start vertex to EVERY OTHER VERTEX if the graph has only positive edge weights.
  2. Dijkstra's Algorithm uses distance as a priority in priority queue
  3. The algorithm is roughly this:
def Dijkstra(S):
    queue.add((S, null, 0)) # current, parent and distance/priority
    distance[S] = 0
    while queue not empty:
        v, parent, _ = queue.pop() # pop out the lowest priority value (shortest distance)
        if visited[v]:
            continue
        visited[v] = True
        distance[v] = distance[parent] + cost(parent, v)
        for neighbour u of v:
            queue.add(u, v, distance[v] + cost(v, u)) # v is parent and the last param is the priority
  1. Bottleneck path: Find path that allows carrying max amount of flow?
  2. Algo is roughly this:
def Bottleneck(S):
    queue.add((S, null, 0)) # current, parent and distance/priority
    distance[S] = 0
    while queue not empty:
        v, parent, _ = queue.pop() # pop out the highest priority value (largest edge)
        if visited[v]:
            continue
        visited[v] = True
        distance[v] = min(distance[parent], cost(parent, v))
        for neighbour u of v:
            queue.add(u, v, min(distance[v], cost(v, u))) # v is parent and the last param is the priority
  1. Min Spanning Tree of G is a spanning tree of G with smallest possible sum of edge weights
  2. PRIMs algo is roughly this:
def PRIM(S):
    queue.add((S, null, 0)) # current, parent and distance/priority aint gonna be used
    while queue not empty:
        v, parent, _ = queue.pop() # pop out the highest priority value (largest edge)
        if visited[v]:
            continue
        visited[v] = True
        T = T union {v, parent}
        for neighbour u of v:
            queue.add(u, v, cost(v, u)) # v is parent and the last param is the priority
  1. A* Algo is some heuristic based search algo
  2. Algorithm is roughly this:
def AStar(S):
    queue.add((S, null, 0)) # current, parent and distance/priority
    distance[S] = 0
    while queue not empty:
        v, parent, _ = queue.pop() # pop out the lowest priority value (smallest heuristic value)
        if distance[v] > distance[parent] + cost(parent, v):
            distance[v] = distance[parent] + cost(parent, v) # found a new smaller path
        else:
            for neighbour u of v:
                queue.add(u, v, distance[v] + cost(v, u) + heuristic(u_)) # v is parent and the last param is the priority

7. Matchings

  1. Definition: Augmenting path given M is a path consisting of
  1. M is a max-matching iff there is no augmenting path
  2. We can find max-matching efficiently if we can find an augmenting path in polytime.
  3. How to find augmenting paths? Use Edmond's Blossom algorithm (implementation details not on the test)
  4. Theorem: We can find augmenting paths in a bipartite graph in O(|E| + |V|) time
  5. Max Matching in Bipartite graphs runtime: O((m+n).n) (Time to find a path using BFS * Max number of augmenting paths)

8. Max Flow and Min Cut

  1. Flow Definition:
  1. Max Flow problem is to find a flow fe that maximizes usfsu
  2. We can solve bipartite matching using max-flow (Add a source and sink vertex. Source connects to all vertices in bipartite graph vertex set A and sink connects to all vertices in bipartite graph vertex set B).
  3. Residual Graphs:
  1. Ford Fulkerson Algorithm:
  1. Runtime for Ford-Fulkerson: O((m+n)*max_flow)
  2. Theorem: Ford-Fulkerson returns the max-flow
  3. Theorem: Max-flow = Min-Cut
  4. There exists a polynomial time algorithm that finds max-st-flow in O((m+n)*m*log(C)) (This is called the Capacity Scaling algorithm)
  5. Capacity Scaling Algorithm is roughly as follows:
f = 0
delta = C # C is max capacity
while delta >= 1:
    1. Construct Residual graph G^f(delta) where we remove edges of G^f with capacity < delta (keep only those edges that are ATLEAST delta amount)
    2. Find st-path in G^f(delta)
        2.1 f <- f + P*delta (go to 1)
    3. Else if no st-path found:
        3.1 delta <- delta / 2
return f
  1. Runtime of Capacity Scaling is: 9. O((m+n)*m*log(C))
  2. Some observations:

9. Linear Programming

  1. Feasible Region: Set of all points x satisfying all constraints
  2. Halfspace: linear inequality
  3. Is feasible region of LP always connection? -> YES
  4. Equalities are allowed in LPs. Strict inequalities are not allowed!
  5. Definition of a Convex Region/Set: A set Sn is CONVEX if for any x,yS we have α(x)+(1α)ySα[0,1]
  6. Any LP can be solved in time that is polynomial in the number of input bits
  7. Will a poly sized LP always have a polysized optimal solution?
  1. Theorem: For any LP, there exists some non-negative linear combination of constraints s.t.

10. LP Duality

  1. Primal LP Objective: maxcTx
  2. Primal LP Constraints: Axb and x0
  3. Primal LP has n variables and m constraints
  4. Dual LP Objective: minbTy
  5. Dual LP Constraints: ATyc and y0
  6. Dual LP has m variables and n constraints
  7. Weak Duality Theorem:
  1. Weak Duality Corollary:
  1. Strong LP Duality Theorem:
  1. Farkas Lemma:
  1. Theorem: There exists a polytime algorithm to check LP-feasibility then there exists a polytime algorithm for LP-Optimization

11. Dynamic Programming

Common Patterns on Test:

  1. LIS: Runtime is O(n2)
    def LIS(self, nums: List[int]) -> int:
        L = len(nums)
        dp = [1]*L
        dp[0] = 1
        max_dp = 1
        for i in range(1, L):
            for j in range(i-1, -1, -1):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], 1+dp[j])
            max_dp = max(max_dp, dp[i])
        return max_dp
  1. Max Independent Sets on Trees: Runtime is O(n)
M[v] = max{
1 + Sum([M(u) for u in grandchildOf(v)]), # includes the root
Sum([M(u) for u in childOf(v)]) # excludes the root
}
  1. Tip: For exam, present solution in form of table structure, base-case, induction-step and runtime-complexity.

12. Knapsack using Dynamic Programming

  1. Note that Knapsack is a NP-Hard problem and hence we don't think it's solvable in polynomial time.
  2. Polytime vs Pseudo-Polytime Algorithms:
  1. Pseudo-Polytime algo for knapsack:
  2. Algo 1: Assume B is small (B = poly(n)) => all si are small
  1. Algo 2: Assume all values are small. All vivmax
  1. PTAS vs FPATS:
  1. Knapsack FPTAS Algo:

13. Markov Decision Process (MDP)

  1. Definition: Finite state space S
  1. Transition Probabilities: Pa(s,s) for aA and s,sS
  1. Goal?: Given time horizon T, design an algorithm (policy) that plays actions to maximize 𝔼[total reward]
  2. Policy Definition: An algorithm (policy) Π tells us what action atπA to take at step t
  3. Denote state of policy Π at step t as Stπ
  4. Goal:Given time horizon T,maxπ𝔼[t=1Tratπ(Stπ)]
  5. We know, for any policy π:
Vπ(s1,T)=ra1π(s1)+s𝒮pa1π(s1,s)·Vπ(s,T1)(reward of 1st step)(Expected reward of $T-1$ steps)V*(s,T)=maxa{ra(s)+s𝒮pa(s,s)·V*(s,T1)}We can calculate using Dynamic Programming (DP):Time horizon:StatesV*(s,T)Space: |𝒮|·TTime: 𝒪(|𝒜|·|𝒮|2·T)



III

Okay, typing out notes got boring real quick as well. I need more mental stimulation. I will live-stream my suffering to further stimulate my brain. At this point I am sleeping only 3-4 hours. I am clearly mentally fatigued but let's fucking keep going fellas:






IV
To retain some theorems and important results, I also made a lot of Flashcards. I'll just upload them here:
  1. Grad Algos Part-1
  2. Grad Algos Part-2
V

Now that the exam is done, here is what happened: I got an A: Wediditfellas

We did it fellas. Note that I did not get an 85+. In fact, I just got an 82. My final grade was 89.37% and the cutoff for an A was 90%. However, more than 80% of the class finished the CIOS survey and hence the grade cutoff was relaxed by exactly one 1%. Everyone who had an 89% got an A.

Final boss of grad-school is defeated. Can't wait to get back to working on real world engineering problems now.

#CS 6515 #georgia-tech #grad-algos