This repository contains a collection of Python implementations of classic algorithms covered in the Algorithms Illuminated book series (better known as the Stanford Algorithms MOOC). So far, I am building correctness and (where applicable for comparisons) efficiency test cases for each algorithm or pulling test cases from the following repository: https://github.com/beaunus/stanford-algs
Most of these implementations are derived directly from pseudo-code.
Key concepts:
- algorithmic complexity analysis through bounding asymptotic growth
- recursive divide-and-conquer approaches with Master Method time complexity analysis of recurrence relations - can we reduce work per-problem fast enough relative to proliferation of sub-problems to reduce total computational complexity?
- relaxation from worst-case time analysis, using randomization for efficient average time complexity and robustness against pathological inputs
Practice implementations:
- Chapter 1 - selection sort, merge sort, recursive integer multiplication, Karatsuba multiplication (optimized recursive integer multiplication)
- Chapter 2 - no programming exercises (asymptotic notation/analysis theory)
- Chapter 3 - merge-sort based O(nlogn) inversion counting
- Chapter 4 - linear search, binary search
- Chapter 5 - quicksort
- Chapter 6 - linear-time selection (of order statistics)
Key concepts:
- graph representations and basic search and topological ordering
- intro to basic data sctructures - stacks, queues, sorted arrays (linked-lists assumed as prior knowledge)
- intro to more advanced data structures - heaps, binary search trees, hash tables, and bloom filters
- applications of advanced data structures and comparisons of supported operations and efficiency per operation on various test problems
Practice implementations:
- Chapter 7 - no programming exercises (intro to graphs)
- Chapter 8 - breadth and depth first search, various extensions including finding connected components in a graph, strongly connected components in a digraph and shortest path lengths in a graph with unit lengths (all linear time complexity)
- Chapter 9 - straighforward Dijkstra's algorithm with O(nm) complexity (n=number of vertices, m=number of edges), both baseline and optimized implementations
- Chapter 10 - custom heap implementation; comparison of custom heap, built-in heap, and linear-time selection for median maintenance; heap-sort
- Chapter 11 - custom binary search tree implementation, comparison with heaps for median maintenance
- Chapter 12 - basic two-sum solution using dictionary to demonstrate hashing application
Key concepts:
- development of and correctness proofs for greedy algorithms, often relying on exchange argument - i.e. assume greedy solution is not optimal then show that this leads to a logical contradiction
- example applications of greedy algorithms - scheduling, Huffman encoding, minimum spanning trees
- dynamic programming - useful when we can enumerate all relevant sub-problems and compute them with a lower computational complexity than a direct recursive solution, caching of computations that are needed multiple times to solve a problem, works when problem has overlapping sub-problems and correct solution is limited to a limited number of optimal sub-problems
Practice implementations:
- Chapter 13 - greedy scheduling, reduces to sorting jobs by the correct key, correct for some problem definitions
- Chapter 14 - Huffman encoding using a straightforward, heap-based, and sorting + queue based implementation
- Chapter 15 - Prim (straightforward and heap-based) and Kruskal's (straight-forward and union-find based) MST algorithms, custom union-find data structure implementation
- Chapter 16 - basic dynamic programming examples; weighted independent set problem; simple knapsack problem with iterative and recursive implementations, demonstration of intractibility of iterative enumeration for a larger problem vs. tractable approach with recursive implementation that solves and caches a smaller sub-set of relevant sub-problems
- Chapter 17 - advanced dynamic programming examples; Needleman-Wunsch similarity score for string sequences; optimal binary search trees when search frequencies are known
- Chapter 18 - shortest paths (including negative edge lengths) with dynamic programming; Bellman-Ford for single-source shortest paths; Floyd-Warshall for all-pairs shortest paths problems
Key concepts:
NP-hard problems can still be attacked but usually requires compromise on accuracy, efficiency, or generality
Local search and MIP can be effective for approximate and exact solutions, both require careful work to formulate the problem for the algorithm
Dynamic programming can help us to a point, with some NP-hard problems
Problems can be proven NP-hard by showing that a known NP-hard problem reduced to the problem, mostly a theoretical consideration but can be helpful to know when facing a problem in practice (e.g. warehouse slotting in my past work)
Chapter 19 - exhaustive search for exact solution to traveling salesman problem
Chapter 20 - greedy (nearest-neighbor) for TSP, 2-sum local search for TSP
Chapter 21 - inefficient exact solutions, Bellman dynamic programming for TSP, a non-commerical MIP for TSP