CS2040C Quiz2 Cheatsheet
CS2040C Quiz2 Cheatsheet
Bubble (Swap bigger with right) - Ω(n), θ(n2), O(n2), O(1) - Modify existing ADT to solve problems
Selection (Smallest swap with front) - Ω(n2), θ(n2), O(n2), O(1) - Adds extra functions/information
Insertion (Smaller swap with left from i to 0) - Ω(n), θ(n2), O(n2), O(1)
Merge (Split till pairs, join) - Ω(n log n), θ(n log n), O(n log n), O(n) Augmented BST (AVLs)
Quick (Partitioning [Smaller, Pivot, Bigger] Repeat in Partitions) - Ω(n log n), θ(n log n), O(n2), O(1) Dynamic Order Stats: Stores node’s weight, updated during insert & rotations
weight(): node->left->w + node->right->w + 1
BST Trees
def select(n, k): def rank(n):
In-Order - Left, Self, Right
# given node and rank, return value # given node, return rank
Pre-Order - Self, Left, Right
r = n->left->w + 1 r = v->left->w + 1
Post-Order - Left, Right, Self
if (k == r) return n->val while (v):
Level-Order - Top-Down, Left-Right (use queue, insert L&R childs when dequeue self)
if (k < r) return select(n->left, k) if (v->parent->left == v): pass
Successor - Find when node > x, then find min in subtree
if (k > r) return select(n->right, k-r) if (v->parent->right == v):
Predecessor - Find when node < x, then find max in subtree
r += v->parent->left->w + 1
GetMin/Max - BST = Ω(1), θ(h), O(n) | AVL = Ω(1), θ(log n), O(log n)
v = v->parent
Search - If node < x, go right; If node > x, go left.
return r
Insert - BST = Ω(1), θ(h), O(n) | AVL = Ω(1), θ(log n), O(log n)
Delete - 0 child = set to null; 1 child = child replace node; 2 child = replace with successor, repeat for subnodes Orthogonal Range Searching (1D Range)
list_query(a, b): list elements, where a ≤ x ≤ b | Time: O(log n + k), where k=no. of output
AVL Trees- max height=O(1.44 * log n) range_count(a, b): count elements, where a ≤ x ≤ b | Time: O(log n)
Height() - max(left, right) + 1 [ total nodes = ∑2h-1 ] - count = rank(b) - rank(a) + 1
Balance Factor = Height(left) - Height(right) def L_Rot(n): def R_Rot(n):
LL Heavy = 1x Right on node tmp = n->r tmp = n->l Orthogonal Range Searching (2D Range)
RR Heavy = 1x Left on node n->r = n->r->l n->l = n->l->r Build x-tree using x coords, then for every x, build y-tree using y coords.
LR Heavy = 1x Left on node->left, 1x Right on node tmp->l = n tmp->r = n Query(): O(logd n + k)
RR Heavy = 1x Right on node->right, 1x Left on node return tmp return tmp Build Tree(): O(n logd-1 n)
Space: O(n logd-1 n)
Hashtable (n=no. of items, m=table size, h=hash func)
Heap - Simpler than AVL (no rotations), better concurrency
Collision Resolution:
Stored in an array, where parent = floor((n-1)/2); child = 2n+1 or 2n+2
Chaining = Use linked list to store collisions in same place | Space: O(m+n)
Ordering: Parent ≥ Child (max heap) or Parent ≤ Child (min heap), nodes as far left as possible
insert(): insert head at linked list | Time: Ω(1), θ(1), O(1)
Height: Always O(log n)
search/delete(): h(key), then find in linked list, delete if needed | Time: Ω(1), θ(1), O(n)
insert(): add new node, then bubble up
Open Addressing = Find another space for colliding items | Space: O(m) delete(): look for node, swap with last node, delete last, then bubble up/down swapped node
insert(): use probe until DELETED or NULL, put value there | Time: Ω(1), θ(1), O(n) extractMax(): Store root value, delete(root), then return value
search/delete(): use probe, set as DELETED (so that further values can find) | Time: Ω(1), θ(1), O(n) increaseKey/decreaseKey(): look for node, replace value, then bubble up/down if inc/dec respectively
All functions are time: O(log n)
def linear(key): def quadratic(key): def double_hashing(key):
return (h(key) + i) % m return (h(key) + i2) % m return (h(key) + [i * g(key)]) % m
Priority Queue - Can be heap or AVL
Pros: Save space (Linked List vs Empty Slots), No mem alloc (no new nodes), better cache performance insert():
Cons: Require good hash func (low collision = better) lower performance as n reaches m. - Sorted array: find insertion location, shift everything over | Time: O(n)
- Unsorted array: Append to end of array | Time: O(1)
expandTable(): double table size, when m/2 items added | O(n)
- AVL: insert into tree | Time: O(log n)
contractTable(): Half table size, when m/4 items deleted | O(n)
extractMin/extractMax():
Application: Verify triangle sharing edges, by hashing each edge and check for collision - Sorted array: Return first/last element for min/max respectively | Time: O(1)
Division Hash Func: h(k) = k % m, where m is prime (is slow) - Unsorted array: Iterate thru array, remove min/max, shift everything over, return min/max | Time: O(n)
Multiplication Hash Func: h(k) = (Ak) mod 2^w >> (w - r), where m=2^r, w=keysize, A=Big odd constant - AVL: find min/max, delete(min)/delete(max) | Time: O(log n)
contains() - look up if item exists in queue
Disjoint Set increase/decreaseKey(): look for item with value, remove item, then insert back with new value
Application: find connectivity (e.g. mazes)
find(): check if p and q in same component (check if connected) HeapSort - O(n log n)
union(): join p and q in same set (connecting together) Steps: Unsorted list A -> Heap -> Sorted list B
- Heapify (Unsorted -> Heap): treat A as heap, then bubble down all elements for A[0..n] | Time: O(n)
Quick-Find: Find is fast, Union is slow, Tree is flat (height 0..1) - Sort (Heap -> Sorted list): If asc order, do extractMax() for B[n..0] | Time: O(n log n)
- Array of component IDs C, where index=element, C[index]=component ID
def find(int p, int q): # Time: O(1) def union(int p, int q): # Time: O(n) Graphs
return C[p] == C[q] for i in range(C.length): - Nodes(vertices)=points in graph; Edges(arcs)=connection between nodes
if (C[i] == C[q]): C[i] = C[p] - Connected/Disconnected: nodes are connected by path/ no path connecting 2 nodes
- Degree: number of adjacent edges from a node
Quick-Union: Find & Union slow, Tree too tall (unbalanced) - Diameter: max distance between 2 nodes, following shortest (least hop) path
- Array of parents P, where index=element, P[index]=parent
Types of Graphs - [Planar (no intersecting edges): V - E + F = 2, where F = faces]
def find(int p, int q): # Time: O(n) def union(int p, int q): # Time: O(n)
- Star (all nodes connected to one central node, like star topology)
while (P[p] != p): p = P[p] while (P[p] != p): p = P[p]
- Clique (aka complete graph, all nodes connect to one another, like mesh topology, total (V2 -V)/2 edges)
while (P[q] != q): q = P[q] while (P[q] != q): q = P[q]
- Line/Path (all nodes in one line, total V-1 edges)
return p == q parent[p] = q
- Cycle (nodes connected form a loop, like ring topology, total V-1 edges)
Weighted-Union: Try keep tree height low, union and find are O(log n)
- 2 arrays of size and parent, where index=element, S[index]=size of component, P[index]=parent Representing Graphs - G = (V, E)
- Adjacency List: Nodes stored in array, Edges in linked list | Space: O(V+E)
def find(int p, int q): # Time: O(log n) def union(int p, int q): # Time: O(log n)
Array of size V, linked list of size E, hence space is O(V+E); if cycle: E=O(V)
while (P[p] != p): p = P[p] // get root parent of p & q (see find)
class Node: int key; LinkedList<int>; | class Graph: Node nodeList[MAX_NODE];
while (P[q] != q): q = P[q] if (S[p] > S[q]): P[q] = p; S[p] += S[q]
return P[p] == q else: P[p] = q; S[q] += S[p]
- Adjacency Matrix: 2D array, boolean val for connection | Space: O(V2)
Weighted-Union + Path Compression: Fastest, almost O(1) - α(m, n), trees are flat Array of size V*V, hence space is O(V2); if cycle: O(V2)
- Array of parents P, but all in same component point to root parent. Accessed with A[a][b], where a and b are nodes
- Calling findRoot will compress the tree (sets parent of each traversed node to root)
Base rules for selecting representation
def findRoot(int p): # Time: O(log n) - If graph is dense |E| = θ(V2), use matrix, else use list
// root = root of p (see find for algo) - If more queries, list is faster (e.g. find all neighbours, enum all neighbours); less queries, matrix is faster
while (P[p] != p): P[p], root = root, P[p] // path compression (find 1 neighbour)
return root
Searching Algorithms (BFS, DFS) - O(V + E); BFS may give different result from DFS
Weighted Graphs (SSSP) Used to find shortest paths (least hops) from start s to end f, graph represented using adjacency list.
Graphs where edges have weights (distance), adjacency list store edge with weights BFS: linear search, does not go backwards (visited[] ensures no loops if graph has cycles)
BFS: only works if all nodes have same weight, then min hops <=> shortest distance DFS: recursive search, go backwards until new edge when stuck
Notation: δ(u,v) = distance from u to v void Graph::BFS(int s) // uses queue (FIFO)
bool visited[_n] = {0}; // mark all false
Bellman-Ford Algorithm - O(V * E) [cannot handle -ve cycles] List<int> queue;
Keep track of δ(u,v) with an array D, relax all edges starting from source (terminate early if relax does nothing) visited[s] = true;
Does not work as SSSP if there is negative-weight cycle (but can use for detecting them) queue.insert(s);
def relax(u, v): while (!queue.empty())
if (D[v] > D[u] + weight(u, v)): D[v] = D[u] + weight(u, v) s = queue.extractMax(); // get top of queue (and print if needed)
for (adj[v].start(); !adj[v].end(); adj[v].next())
def BellmanFord(): # Time: O(log n) if (!visited[adj[v].current()])
dist[V] = {INFINITE} visited[adj[v].current()] = true;
for n in range(V): // for every node queue.insert(adj[v].current());
for e in edges: relax(n, e) // for every edge of node
void Graph::DFS(int v) // uses stack (LIFO)
Dijkstra’s Algorithm - O(E log V) [cannot handle -ve weights (non modified ver.)] visited[v] = true; // Mark the current node as visited (and print if needed)
Put all vertices in priority queue with priority as INFINITY (excl start), and distance array D = {MAX_INT} // Recurse for all adjacent nodes
for all nodes, extractMin() to get lowest weight, relax its adjacent nodes and update: for (adj[v].start(); !adj[v].end(); adj[v].next())
- D[v] if D[v] > D[u] + weight if ( !visited[adj[v].current()] ) DFS(adj[v].current());
- insert(v, D[u] + weight) (or decreaseKey() depending on implementation)
- update visited[u], if any Topological Sort - O(V + E)
Sequential ordering of nodes in Directed Acyclic Graphs (DAG)*, where edges only point forward
def relax(u, v, w):
Uses Post-Order DFS, which gives full sorted directed graph.
if (D[v] > D[u] + w): // shorter dist found
D[v] = D[u] + w void Graph::DFS(int v) // uses stack (LIFO) [Done on the Sequential ordered nodes]
parent[v] = u // keep track of path visited[v] = true; // Mark the current node as visited (and print if needed)
queue.decreaseKey(v, D[v]) // ensures each edge relaxed once // Recurse for all adjacent nodes
for (adj[v].start(); !adj[v].end(); adj[v].next())
def dijkstra(s, f): if ( !visited[adj[v].current()] )
for n in V: queue.insert(n, n==s ? 0 : INFINITY) DFS(adj[v].current());
while (!queue.empty()): stack.push(v); // store v in rev order,
n = queue.extractMax()
*DAG = no nodes in graph will point backwards. [CAN FIND LONGEST/SHORTEST PATH in DAGs]
for v in n->adj: relax(n, v, v->w)
MST (Minimum Spanning Trees)
An acyclic subset of edges that connect a tree (with minimum weights)
Properties
1. No cycles - MSTs do not have any cycles
2. ??? - Cutting an MST at an edge will give 2 MSTs
2. Cycle Property - For every cycle in graph, maximum edge will not be in MST
3. False Cycle Property - For every cycle, the minimum edge may or may not be in the MST
4. Cut Property - For every component, the minimum weight connecting them will be in the MST
4b. For every vertex, the minimum outgoing edge is always in the MST
If all edges have same weight, use BFS/DFS, it will return a MST.
For Maximum Spanning Trees, change all weights to negative. The resultant Minimum Spanning Tree ⇔
Maximum Spanning Tree
niggers
Computational Geometry
2D Convex Hull 3D Convex Hull
Weighted Union (obj, size, parent arrays)
- Jarvis’ March (Gift Wrapping) - O(hn) - Divide & Conquer - O(n log n)
bool find(T p, T q)
- Graham Scan - O(n log n) - Incremental Method - O(n log n)
- Divide & Conquer - O(n log n) - Quickhull - worst O(n2), expected O(n log n) while (parent[p] != p) p = parent[p]
- Incremental Method - O(n log n) while (parent[q] != q) q = parent[q]
return p == q
BSP Trees
Advantage void union(int p, int q)
- Once tree’s computed, can handle all viewpoints without reconstruction (efficient)
- Handles transparency // get root parent of p & q (see find)
- Standard format for storing environment (for games) if (p_size > q_size)
Disadvantages parent[q] = p
- Can’t handle moving/changing environments p_size += q_size
- Preprocessing time for tree construction is long else
// do opposite; bigger set -> parent
BSP Trees
Advantage
- Once tree’s computed, can handle all viewpoints without reconstruction (efficient) int findRoot(int p)
- Handles transparency // get root of p (see find)
- Standard format for storing environment (for games) while (parent[p] != p) // path compressn
Disadvantages
parent[p], root = root, parent[p]
- Can’t handle moving/changing environments
- Preprocessing time for tree construction is long return root
Sorting Algorithms:
Bubble Sort (Biggest to end) Insertion Sort (Sort iteratively) Graph
for (i = 0; i < n - 1; i++) for (i = 1; i < n; i++) void Graph::DFS(int v) // uses stack (LIFO)
for (j = 0; j < n - i - 1; j++) key = arr[i]; // Mark the current node as visited (print if needed)
if (arr[j] > arr[j + 1]) j = i - 1; visited[v] = true;
swap(arr[j], arr[j + 1]); while (j >= 0 && arr[j] > key) // Recurse for all adjacent nodes
arr[j + 1] = arr[j]; for (adj[v].start(); !adj[v].end(); adj[v].next())
Selection Sort (Smallest to start) j = j - 1; if ( !visited[adj[v].current()] ) DFS(adj[v].current());
for (i = 0; i < n-1; i++) arr[j + 1] = key;
min_idx = i;
void Graph::BFS(int s) // uses queue (FIFO)
for (j = i+1; j < n; j++) Merge Sort bool visited[_n] = {0}; // mark all false
if (arr[j] < arr[min_idx]) if (begin >= end)
List<int> queue;
min_idx = j; return;
visited[s] = true;
if (min_idx!=i) auto mid = begin + (end - begin) / 2;
queue.insert(s);
swap(&arr[min_idx], &arr[i]); mergeSort(array, begin, mid);
mergeSort(array, mid + 1, end);
while (!queue.empty())
Quick Sort merge(array, begin, mid, end);
s = queue.extractMax(); // get top of queue
if (low < high)
for (adj[v].start(); !adj[v].end(); adj[v].next())
int pi = partition(arr, low, high); void merge(int arr[], int l, int m, int r)
if (!visited[adj[v].current()])
quickSort(arr, low, pi - 1); auto const leftI = m - l + 1;
visited[adj[v].current()] = true;
quickSort(arr, pi + 1, high); auto const rightI = r - m;
queue.insert(adj[v].current());
int partition(int arr[], int low, int high) // Copy into temp arrays (left & right)
int pivot = arr[high]; // leftArr = arr[l:leftI] Graph
int i = (low - 1); // rightArr = arr[m+1:rightI] PriorityQueue pq = new PriorityQueue();
for (int j = low; j <= high - 1; j++) for (Node v : G.V()) {
if (arr[j] < pivot) { while (leftC < leftI && rightC < rightI) { pq.insert(v, INFTY);
i++; if (leftArr[leftC] <= rightArr[rightC]) { }
swap(&arr[i], &arr[j]); arr[mergeI] = leftArr[leftC]; pq.decreaseKey(start, 0);
swap(&arr[i + 1], &arr[high]); leftC++; // init set S (check for dupes i.e. if node alrd added)
return (i + 1); } else { HashSet<Node> S = new HashSet<Node>();
// do the opposite S.put(start);
}
mergeI++; // init parent hash table
HashMap<Node,Node> parent = new HashMap<Node,Node>();
// Copy remaining elements into parent.put(start, null);
// either L/R array
while (!pq.isEmpty()){
Node v = pq.deleteMin();
S.put(v);
for each (Edge e : v.edgeList()){
Node w = e.otherNode(v);
if (!S.get(w)) {
pq.decreaseKey(w, e.getWeight());
parent.put(w, v);