feat: more content

This commit is contained in:
Yadunand Prem 2023-11-30 20:15:32 +08:00
parent 7bf132f456
commit 7836c9185c
No known key found for this signature in database

View File

@ -2,12 +2,16 @@
| Algorithm | Best Time Complexity | Average Time Complexity | Worst Time Complexity | InPlace | Stable |
| --------- | ------------------------ | -------------------------- | --------------------- | ------- | ------ |
| Bubble | $O(n)$ comp, $O(1)$ swap | $O(n^2)$ comp, swap | $O(n^2)$ comp, swap | Yes | Yes |
| Selection | $O(n^2)$, $O(1)$ swap | $O(n^2)$ comp, $O(n)$ swap | $O(n^2)$, O(n) swap | Yes |
| Selection | $O(n^2)$, $O(1)$ swap | $O(n^2)$ comp, $O(n)$ swap | $O(n^2)$, O(n) swap | Yes | No |
| Insertion | $O(n)$, $O(1)$ swap | $O(n^2)$ comp, swap | $O(n^2)$ comp, swap | Yes | Yes |
| Merge | $O(n \log(n))$ | $O(n \log(n))$ | $O(n \log(n))$ | No | Yes |
| Quick | $O(n \log(n))$ | $O(n \log(n))$ | $O(n^2)$ | Yes | No |
| Counting | $O(n + k)$ | $O(n + k)$ | $O(n + k)$ |
| Radix | $O(nk)$ | $O(nk)$ | $O(nk)$ | No | Yes |
Counting: O(N+k),
Radix: $O(w\times (N+k))$: Each item to sort as a string of w digits, sort by rightmost digit (using stable counting sort), k is radix
### Bubble
Invariant: After `i` iterations, largest `i` elements are sorted at the back of the array
```java
@ -21,7 +25,6 @@ static void sort(int[] input) {
if (input[i] > input[i+1]) swap(input, i, i+1);
} } }
```
### Selection
Invariant: After `i` iterations, smallest `i` elements are sorted to the front of the array
```java
@ -35,10 +38,8 @@ static void sort(int[] input) {
```
### Insertion
Invariant: At the end of kth iteration, leftmost `k` elements are sorted (but not in final correct position)
1. Outer loop executes N-1 times
2. inner loop runs O(n) if arrayis reverse sorted
```java
static void sort(int[] arr) {
// iterate through each element in the array
@ -79,15 +80,274 @@ void quickSort(array A, int low, int high) {
}
```
## Linked List
- Stack: Last In First Out, Push and Pop from Head
- Queue - First In First Out, Push to tail, pop from head
## Binary Heap
## Union Find
- Height: floor$(\log N)$, Left Node: $2n$, Right Node: $2n+1$, parent node = $\frac{n}{2}$
- Min Comparisions: n-1, min swaps: 0
- Max comparisons: 2(n) - 2(no of 1s) - 2(trailing 0 bits)
- Max swaps: $\frac{n}{2}$ until `x=1` then sum
```pseudocode
insert(node) {
arr.append(node)
bubbleUp(arr)
}
bubbleUp(node) {
while(A[i] > A[parent(i)]) {
swap(A[i], A[parent(v)]);
v = parent(i);
}
}
bubbleDown(node) {
A[1] = A[A.length-1]
i = 1; A.length--;
while(i < A.length)
if A[i] < (L = larger of i's children) {
swap(A[i], L); i = L
}
}
optimised heapify() {
for (i = A.length/2; i >= 1; --i) shiftDown(i);
}
```
- Change value at index
- Update value
- If new value > old node, then bubbleUp, else bubbleDown
- Root element is the largest, 2nd largest element is child of root, 3rd largest doesn't have to be
- Max swaps: n / 2 integer divide until x = 1 and then sum
- Max comparisons: 2n - 2(num of 1s) - 2(trailing 0s)
### Invariants
- Every vertex is greater than every value of its children
- Must be a complete binary tree
- Height is always log(N), as every level has to be filled
## Union Find $O(\alpha N)$
- MIN height needed = $2^h$ nodes
- Keep track of rank[i], uppperbound height of a subtree rooted at vertex i
- `findSet(i)`: From vertex i, recursively go up the tree, until root of tree. Path compression used after each call of findSet
- `IsSameSet(i,j)`: Check if `FindSet(i) == FindSet(j)`
- `unionSet(i,j)`: Shorter tree joined to the taller tree (rank comes into play)
```python
def union(a,b):
ap = findSet(a)
bp = findSet(b)
if rank[ap] < rank[bp]: parents[ap] = bp
elif rank[bp] < rank[ap]: parents[bp] = ap
else: parent[bp] = ap; rank[ap] += 1
```
## Hash Table
The hash table size (modulo) sould be a large prime about 2x size of expected number of keys
```python
def hash_string(s):
sum = 0
for c in s: sum = (sum*26)%M + ord(c)%M
return sum
```
- Open Addressing: All keys in single array, collision resolved by probing alternative address
- Closed Addressing: Adjacency List, append collided keys to auxiliary data structure
### Linear
`i = (h(v) + k*1)%M`
- On deletion set the value to removed, it knows that the values after it are valid during probing
- If the values tend to cluster, linear probing is bad
### Quadratic
`i = (h(v) + k*k)%M`
- Issues is that we might have infinite cycles with this scheme.
- If $\alpha < 0.5$ and M is prime, then we can always find empty slot
### Double Hashing
`i = (h(v) + k*h2(v))`, h2 is usually a smaller prime than M.
## Binary Search Trees
- Height: $O (\log_2(N)) < h < N$
- BST Property: For vertex X, all vertices on the left subtree of X are strictly smaller than X and all vertices on the right subtree are strictly greater than X
- `minHeight=ceil(log(n))-1`
- `maxHeight=n-1`
```python
def search(target):
if curr == None: return None
if curr == target: return curr
if curr < target: return search(curr.right)
return search(curr.left)
def left(node): return node if node.left == None else return left(node.left)
def successor(node):
if node.right != null: return left(node.right)
p = node.parent, T = node
while(p != null and T == p.right): T = p, p = T.parent
if p == null: return None
return p
def remove(node, key):
if node == None: return None
if node.val < key: node.right = remove(node.right, key)
if node.val > key: node.left = remove(node.left, key)
if node.left == None and node.right == None:
node == None
elif node.left == None and node.right != None:
node.right.parent = node.parent
node = node.right
elif node.right == None and node.left != None:
node.left.parent = node.parent
node = node.left
else: # both nodes exist, find successor
successorV = successor(key)
node.val = successorV # replace this with successor
delete(T.right, successor) # delete successor
```
```python
def validBst(node, minV, maxV):
if not node: return True
if node.val <= minV or node.val >= maxV:
return False
left = validBst(node.left, minV, node.value)
right = validBst(node.right, node.value, maxV)
return left and right
```
## BBST AVL Tree
- each node augmented with height
- height: $h < 2\log(N)$
- `height=-1` (if empty tree), `height=max(left.h, right.h)+1`, computed at end of `insert(v)` or `remove(v)` operation
- `BF(n)=left.h-right.h`
- height balanced IF $|$left.h $-$right.h$|\leq 1$
```python
def h(node): node.height if node else -1
def rotateLeft(node):
if node.right == None: return node
w = node.right
w.parent = node.parent
node.parent = w
if (w.left != None): w.left.parent = node
w.left = node
node.height = max(h(node.left), h(node.right))+1
w.height = max(h(w.left), h(w.right))+1
return w
def balance(node):
bal = h(node.left) - h(node.right)
if bal == 2: # left heavy
bal2 = h(node.left.left)-h(node.left.right)
if bal2 != 1: node.left = rotateLeft(node.left)
node = rotateRight(node)
elif bal == -2:
bal2 = h(node.right.left)-h(node.right.right)
if bal2 != -1: node.right = rotateRight(node.right)
node = rotateLeft(node)
def insert(node, val):
if node == None: return Node(val)
if node.key < val:
node.right = insert(node.right, v)
node.right.parent = node
if node.key > val:
node.left = insert(node.left, v)
node.left.parent = node
node = balance(node)
node.height = max(h(node.left), h(node.right)) +1
return node
```
### Invariant
## Graph Structures
### Trees
- V vertices, E = V-1 edges, acyclic, 1 unique path between any pair of vertices
- Root a tree by running BFS/DFS from the root
```python
def isTree(AL, directed=True):
def dfs(node, visited, parent):
visited[node] = True
for neighbour in AL[node]:
if not visited[neighbour]:
if dfs(neighbour, visited, node): return True
elif neighbour != parent: return True
return False
n = len(AL)
visited = [False]*n
if dfs(0, visited, -1):
return False #cycle detected.
if False in visited:
return False # unconnected
edges = sum(len(n) for n in AL.values())
if directed:
if edges != n-1: return False
else:
if edges != 2*(n-1): return False
return True
```
### Complete Graph
- $V$ vertices, $E=\frac{V\times(V-1)}{2}$ edges
```python
def isComplete(AL):
n = len(AL)
for i in range(n):
for j in range(i+1, n):
if j not in AL[i]: return False
```
### Bipartite
- V vertices that can be split to 2 sets, where no edge between members of same set
- no odd length cycle
- Complete Bipartite: all V from one set connected to all V from other set.
```python
def isBipartite(AL):
n = len(AL)
colors = [-1]*n
for start in range(n): # incase it isn't connected
if colors[start] == -1:
q = deque([(start,0)]) # (vertex,color)
colors[start] = 0
while q:
curr, color = q.popleft()
for neighbour in AL[curr]:
if colors[neighbour] == -1:
colors[neighbour] = 1-color # flip color
q.append((neighbour, 1-color))
elif colors[neighbour] == color:
return False
return True
```
### DAG
- Directed, no cycle
```python
def isDag(AL):
def dfs(node, visited, stack):
visited[node] = True
stack[node] = True
for n in AL[node]:
if not visited[n]:
if dfs(n, visited, stack):
return True # pass it down
elif stack[n]:
return True # back edge found
stack[node] = False
return False
n = len(AL)
visited = [False]*n
stack = [False]*n
for i in range(n):
if not visited[i]:
if dfs(i, visited,stack):
return False
return True
```
```python
def isDagBfs(AL):
n = len(AL)
inDeg = [0]*n
for nbs in AL:
for nb in nbs:
inDeg[nb] += 1
q = deque([v for v in range(n) if inDeg[v] == 0]) # queue of 0 indeg
while q:
curr = q.popleft()
for nb in AL[curr]:
inDeg[nb] -= 1
if inDeg[nb] == 0: q.append(nb)
return all(i == 0 for i in inDeg) # all inDeg == 0
```
### Storage
- Adjacency Matrix - Check existance of edge in $O(1)$. Storage $O(V^2)$
- Adjacency List - Storage $O(V+E)$
- Edge List - Storage $O(E)$
## Graph Traversal
### Topo Sorting
#### Lexographic Kahn's Algorithm $O(V\log(V)+E)$
- Reachability Test: DFS/BFS and check if visited
- ID CCs: run DFS on a vertex. all visited=true is in same CC
- count CCs: for all u in V, if unvisited, incr CCCount, then DFS
### Lexographic Kahn's Algorithm (Topo Sort) $O(V\log(V)+E)$
Non Lexographic Variant is O(V+E)
```python
from heapq import heappush, heappop
@ -114,7 +374,21 @@ def topoSort(AL):
return res
```
## Single Source Shortest Path
```python
def relax(from,to, weight):
if dist[v] > dist[u]+weight: # can be shortened
dist[v] = d[u]+weight
path[v] = u
return True
return False
```
- On unweighted graphs: BFS
- On graphs without negative weight: Dijkstra
- On graphs without negative weight cycles: Modified Dijkstra
- On Tree: BFS/DFS
- On DAG: DP
### Bellman Ford Algorithm $O(V\times E)$
- Can detect negative weight cycles
```python
def bellman_ford(AL, numV, start):
dist = [INF for _ in range(V)]
@ -138,11 +412,38 @@ def bellman_ford(AL, numV, start):
print(start, u, dist[u])
```
### Breadth First Search
Instead of standard visited array, replace with dist[] array, where initial distances are infinite. Then set source to 0. if D[v] = inf, then set it to d[u]+1
### Dijkstra's Algorithm $O((V+E)\log Vk)$
#### Modified Dijkstra's Algorithm
### Dynamic Programming
- BFS with a priority queue
```python
def dijkstra(AL, start):
V = len(AL)
dist = [float('inf') for u in range(V)]
dist[start] = 0
pq = []
heappush(pq, (0, start))
while pq:
dist, u = heappop(pq)
if (dist > dist[u]): continue
for v, w in AL[u]:
if dist[u]+w >= dist[v]: continue
dist[v] = dist[u]+w
heappush(pq, (dist[v], v))
```
### DFS O(V)
DFS can be used to solve SSSP on a weighted tree. Since there is only 1 unique path that connects source to another vertex, that path is teh shortest path
### Dynamic Programming O(V+E)
SSSP on a DAG. Find the topo sort and relax in the order of the topo sort.
```python
order = khan_toposort() # O(V+E)
while not order.empty():
u = order.popleft()
for v, w in AL[u]: relax(u, v, w)
```
## Minimum Spanning Tree
- Tree that connects to all vertices of G
### Kruskal's Algorithm $O(E\log(V))$
- sort edges, then loop over these edges, and greedily take the next edge that does not cause cycles
If the weight of the edges is bounded, then you can use counting sort to bring the complexity down to O(E)
```python
def kruskalMST(EL, numV, numE): # edge list has (weight, from, to)
@ -159,7 +460,9 @@ def kruskalMST(EL, numV, numE): # edge list has (weight, from, to)
UF.unionSet(u, v)
print(cost)
```
### Prim's Algorithm
### Prim's Algorithm $O(E \log(V))$
- Starts from a vertex, and queues all edges to PQ
- if head vertex has not been visited, take that edge and repeat from 1
```python
def prims(AL, numV):
pq = []
@ -179,3 +482,13 @@ def prims(AL, numV):
if not taken[v]: heappush(pq, (w, v))
print(cost)
```
## Miscellaneous
- make a graph where the vertices are a pair <rectangle, horizontal/vertical>
- define the edges as the following
- for each <rectangle, vertical>, make a directed edge to <neighboring rectangle in vertical direction, horizontal>
- for each <rectangle, horizontal>, make a directed edge to <neighboring rectangle in horizontal direction, vertical>
- (still undefined: what is the weight?)
- run multi-source shortest path, where the sources are <starting rectangle, vertical> and <starting rectangle, horizontal>
- (still undefined: what shortest path algorithm to be used here?)
- take the shortest path to <target rectangle, vertical> as the answer
- (is this correct? any other vertex to consider?)