Module 2 Β· days 21–31 Β· cheatsheet

Data Structures Visualized

The whole module on one page β€” analogy on the left of your memory, definition on the right. Print it (Ctrl/Cmd+P) and stick it above your desk.

1.Arrays as BookshelvesNumbered slots in a row

An array is a contiguous block of equal-size elements, so the address of index i is base + i Γ— size β€” O(1) random access. Insertion or deletion at index i requires shifting the following elements, making it O(n)..

a = [12, 30, 21, 45, 9, 38, 27]

a[6]        # one multiply, one add, one read
# address = base + 6 * itemsize
# slots 0..5 are never touched β€” O(1)

len(a)      # also O(1), it is stored
2.Dynamic ArraysMoving to a bigger shelf

A dynamic array holds a capacity larger than its length; on overflow it allocates a larger buffer (typically 2Γ—) and copies. A single resize is O(n), but doubling makes the amortised cost of append O(1)..

# CPython list append, in essence:
def append(self, v):
    if self.length == self.capacity:
        self.capacity = self.capacity * 2   # O(n) copy
        self.buf = self.buf + [None] * self.capacity
    self.buf[self.length] = v               # O(1)
    self.length += 1

# amortised O(1) because capacity DOUBLES
3.Linked Lists as Treasure HuntsEach clue points to the next

A linked list stores each element in a node holding a value and a pointer to the next node. There is no index arithmetic, so access is O(n) traversal, but insertion or removal given a node reference is O(1) β€” only pointers are rewritten, nothing shifts..

class Node:
    def __init__(self, value, nxt=None):
        self.value, self.next = value, nxt

def insert_after(node, value):
    node.next = Node(value, node.next)
    return node.next
# two writes. nothing shifts.
4.Doubly Linked ListsFootprints in both directions

Each node holds next and prev pointers, enabling O(1) removal given only the node (no predecessor search) and bidirectional traversal, at the cost of one extra pointer per node and more pointer updates per mutation..

def remove(node):
    node.prev.next = node.next
    node.next.prev = node.prev
# O(1) β€” no search for a predecessor

from collections import deque
d = deque([4, 8, 15])
d.appendleft(1)   # O(1) at BOTH ends
d.pop()           # O(1)
5.Stacks as Tray PilesLast tray on, first tray off

A stack is LIFO: push and pop both operate at one end in O(1). It is the structure behind function call frames, undo histories and depth-first traversal β€” anywhere you must finish the most recent thing before returning to the previous one..

stack = []
stack.append(4)      # push
stack.append(9)
stack.append(2)

stack.pop()          # 2  β€” last in, first out
stack[-1]            # 9  β€” peek

# a plain list IS the idiomatic Python stack
6.Queues as Lunch LinesFirst in line eats first

A queue is FIFO: enqueue at the tail, dequeue from the head, both O(1) with a linked list or ring buffer. It underlies breadth-first traversal, task scheduling and any producer/consumer buffer..

from collections import deque

q = deque()
q.append("Ada")      # join the back
q.append("Bo")

q.popleft()          # "Ada" β€” first in, first out

# never use list.pop(0) β€” that is O(n)
7.Hash Maps as DictionariesStraight to the right page

A hash function maps a key to a bucket index, giving average O(1) insert and lookup. Performance depends on a good hash and a low load factor; the map resizes and rehashes as it fills to keep buckets short..

def my_hash(key, n_buckets):
    total = 0
    for ch in key:
        total += ord(ch)
    return total % n_buckets

d = {"cat": 1, "dog": 2}
d["dog"]      # straight to the bucket β€” O(1)
8.CollisionsTwo words on one page

Collisions occur when distinct keys hash to the same bucket. Separate chaining stores a list per bucket; open addressing probes for the next free slot.

buckets = [[] for _ in range(5)]

def put(k, v):
    i = my_hash(k, len(buckets))
    buckets[i].append((k, v))   # chain, do not overwrite

def get(k):
    i = my_hash(k, len(buckets))
    for key, val in buckets[i]:  # scan the short chain
        if key == k:
            return val
9.Trees as Org ChartsOne boss, many reports

A tree is an acyclic connected graph with a root and a parent-child hierarchy. In a balanced binary search tree the ordering invariant lets you discard half the remaining nodes at each step, giving O(log n) search, insert and delete..

def search(node, t):
    if node is None:
        return None
    if t == node.value:
        return node
    if t < node.value:
        return search(node.left, t)
    return search(node.right, t)
# each comparison discards half the tree
10.Graphs as Friendship MapsEveryone knows everyone

A graph is a set of vertices and edges, directed or undirected, possibly cyclic. It is stored as an adjacency list (space O(V+E), good for sparse graphs) or an adjacency matrix (O(VΒ²), constant-time edge lookup).

adj = {
    "A": ["B", "C"],
    "B": ["A", "D", "E"],
    "C": ["A", "F", "G"],
}

len(adj["B"])        # degree of B
"C" in adj["A"]      # is there an edge?
# space O(V + E) β€” right for sparse graphs