Data Structures & Algorithms

Every structure and algorithm a syllabus expects, with the complexities that get asked and the conditions under which the textbook answer is wrong.

If you remember nothing else

  • Big-O is an upper bound, Big-Omega a lower bound, Big-Theta a tight bound — worst case and Big-O are different axes, not synonyms.
  • Quicksort is O(n log n) average but O(n²) worst case; mergesort is O(n log n) always but needs O(n) extra space; heapsort is O(n log n) in place but unstable.
  • A hash table is O(1) average and O(n) worst case — the worst case is every key colliding.
  • Dijkstra fails on negative edge weights; Bellman-Ford handles them and detects negative cycles.
  • Dynamic programming applies when a problem has optimal substructure AND overlapping subproblems; without overlap, plain divide and conquer is the right tool.
  • Inorder traversal of a BST yields the keys in sorted order — that single fact answers a surprising number of questions.

Complexity analysis

Asymptotic notation describes how cost grows as input size grows, ignoring constants and lower-order terms. The point is to compare algorithms independently of machine speed.

NotationBoundRead asFormally
UpperGrows no faster than for some and all
LowerGrows at least as fast as for some and all
TightGrows exactly like Both and hold
Strict upperGrows strictly slower than Holds for every

The growth hierarchy

Anything at or below n log n is generally practical; 2^n and n! are usable only for tiny inputs.

Amortised analysis

The average cost per operation across a worst-case sequence of operations. A dynamic array's push is O(n) when it must resize, but resizing doubles the capacity, so the O(n) copies are spread over n cheap pushes — the amortised cost is O(1). Note this is not the same as average-case analysis: amortised is a worst-case guarantee over a sequence, with no probability involved.

The master theorem

For a recurrence of the form with , — compare against :

CaseConditionResultExample
1 — leaves dominateBinary tree traversal:
2 — balancedMergesort:
3 and regularity holds — root dominates

Arrays, linked lists, stacks and queues

OperationArrayDynamic arraySingly linked listDoubly linked list
Access by indexO(1)O(1)O(n)O(n)
Search (unsorted)O(n)O(n)O(n)O(n)
Insert at frontO(n)O(n)O(1)O(1)
Insert at backO(1) amortisedO(n), or O(1) with a tail pointerO(1)
Delete a known nodeO(n)O(n)O(n) — need the predecessorO(1)
Memory overheadNoneSlack capacityOne pointer per nodeTwo pointers per node
Cache localityExcellentExcellentPoorPoor

Stacks and queues

StackQueueDequePriority queue
DisciplineLIFOFIFOBoth endsBy priority
Operationspush, pop, peekenqueue, dequeue, frontpushFront/Back, popFront/Backinsert, extractMin
All O(1)?YesYesYesNo — O(log n) via a heap
Classic usesCall stack, expression evaluation, DFS, undo, bracket matchingBFS, scheduling, buffering, producer-consumerSliding-window maximum, work stealingDijkstra, Huffman coding, task scheduling
CTwo questions that come up constantly
// 1. Detect a cycle in a linked list - Floyd's tortoise and hare, O(n) time O(1) space int hasCycle(Node *head) { Node *slow = head, *fast = head; while (fast && fast->next) { slow = slow->next; // one step fast = fast->next->next; // two steps if (slow == fast) return 1; // they can only meet inside a cycle } return 0; } // 2. Reverse a singly linked list, iteratively Node *reverse(Node *head) { Node *prev = NULL, *cur = head; while (cur) { Node *next = cur->next; cur->next = prev; prev = cur; cur = next; } return prev; // prev is the new head }

Hashing

A hash table maps a key to a bucket index via a hash function, giving O(1) average lookup. Everything interesting is about what happens when two keys land in the same bucket.

Collision strategyHow it worksTrade-off
Separate chainingEach bucket holds a linked list (or tree) of entriesSimple, degrades gracefully, tolerates load factor > 1; costs pointers and loses locality
Linear probingOn collision try the next slot, wrapping aroundExcellent cache locality; suffers primary clustering — runs of full slots grow and merge
Quadratic probingTry slots awayBreaks primary clustering; introduces secondary clustering and may not probe every slot
Double hashingStep size is itself a second hash of the keyBest distribution of the open-addressing schemes; two hash computations per probe
OperationAverageWorst case
InsertO(1)O(n)
SearchO(1)O(n)
DeleteO(1)O(n)

The worst case is every key hashing to one bucket. Java 8 mitigates it by converting a bucket to a red-black tree past eight entries, capping the worst case at O(log n).

Trees and binary search trees

  • Height of a node — longest path down to a leaf, in edges. Depth — path up to the root. A single node has height 0; an empty tree has height −1.
  • Full binary tree — every node has 0 or 2 children. Complete — every level filled except possibly the last, which fills left to right. Perfect — all leaves at the same depth, every internal node has 2 children.
  • A perfect binary tree of height has nodes and leaves.
  • A binary tree with nodes has height at least and at most .

Traversals

TraversalOrderYieldsTypical use
InorderLeft, Node, RightFor a BST: sorted orderRetrieve sorted keys; validate a BST
PreorderNode, Left, RightRoot firstCopy or serialise a tree; prefix expressions
PostorderLeft, Right, NodeChildren before parentDelete a tree; evaluate an expression tree; postfix
Level orderBreadth-first, by depthLevel by levelPrint by level; find shortest path in an unweighted tree

The first three are depth-first and naturally recursive (O(h) stack). Level order needs an explicit queue.

BST operationBalancedDegenerate (sorted insertion)
SearchO(log n)O(n)
InsertO(log n)O(n)
DeleteO(log n)O(n)

Inserting already-sorted keys into a plain BST produces a linked list. This is exactly why balanced trees exist.

BST deletion — the three cases

  1. Leaf node

    Remove it and null the parent's pointer.
  2. One child

    Splice the child into the deleted node's position.
  3. Two children

    Replace the node's key with its inorder successor (the leftmost node of the right subtree) or its inorder predecessor, then delete that successor — which by construction has at most one child, reducing to an earlier case.

Balanced trees: AVL, red-black and B-trees

AVL treeRed-black treeB-tree / B+ tree
Balance ruleHeight of the two subtrees differs by at most 1Five colour rules; no red node has a red childAll leaves at the same depth; each node holds between and keys
Height bound — more rigidly balanced — very shallow for large
LookupFaster (shorter tree)Slightly slowerFew node visits, but each is a disk block
Insert/deleteMore rotationsAt most 2 rotations for insert, 3 for deleteSplits and merges
Best forRead-heavy in-memory workloadsMixed read/write; the general-purpose defaultDisk-resident indexes
Used bySome in-memory databasesstd::map, Java TreeMap, Linux CFS schedulerDatabase indexes, filesystems (NTFS, ext4)
  • AVL rotations — four cases. Left-Left needs one right rotation; Right-Right one left rotation; Left-Right a left then a right; Right-Left a right then a left.
  • Red-black rules — every node is red or black; the root is black; all leaves (NIL) are black; a red node has only black children; every root-to-leaf path contains the same number of black nodes.
  • The last two rules together force the longest path to be at most twice the shortest, which is where the bound comes from.

Heaps and priority queues

OperationComplexityMechanism
Find min/maxO(1)It is the root
InsertO(log n)Append at the end, then sift up
Extract min/maxO(log n)Swap root with last, shrink, then sift down
Decrease keyO(log n)Lower the value, sift up
Build heap from n itemsO(n)Sift down from the last internal node backwards
Search arbitrary keyO(n)No ordering between siblings

Graphs: representation and traversal

Adjacency matrixAdjacency list
Space
Edge exists?O(1)
Enumerate neighbours
Add an edgeO(1)O(1)
Best forDense graphs; algorithms needing fast edge lookupSparse graphs — nearly all real ones
BFSDFS
Data structureQueueStack (or recursion)
OrderLevel by level from the sourceAs deep as possible, then backtrack
Complexity
Space — can be the whole frontier — the current path depth
FindsShortest path in an unweighted graphCycles, topological order, connected components, bridges and articulation points
Also used forBipartiteness checking, web crawling by levelMaze solving, backtracking, strongly connected components
PythonCycle detection differs by graph type
# Undirected: any visited neighbour that is not the parent closes a cycle. def has_cycle_undirected(g, u, parent, seen): seen.add(u) for v in g[u]: if v not in seen: if has_cycle_undirected(g, v, u, seen): return True elif v != parent: return True # back edge return False # Directed: needs three colours. A grey neighbour means a back edge; a black # one is merely a cross edge to a finished subtree and is NOT a cycle. WHITE, GREY, BLACK = 0, 1, 2 def has_cycle_directed(g, u, colour): colour[u] = GREY for v in g[u]: if colour[v] == GREY: return True if colour[v] == WHITE and has_cycle_directed(g, v, colour): return True colour[u] = BLACK return False

Shortest paths, spanning trees and ordering

AlgorithmSolvesComplexityNegative weights?
DijkstraSingle-source shortest path with a binary heapNo — fails silently
Bellman-FordSingle-source shortest pathYes, and detects negative cycles
Floyd-WarshallAll-pairs shortest pathsYes (no negative cycles)
KruskalMinimum spanning treeYes — weights may be negative
PrimMinimum spanning tree with a heapYes
Topological sortLinear order of a DAGN/A — unweighted
  • Bellman-Ford relaxes every edge times. If a -th pass still improves a distance, a negative cycle is reachable.
  • Topological sort exists iff the graph is a DAG. Two methods: repeatedly remove a vertex of in-degree 0 (Kahn's algorithm), or reverse the DFS finish order.
  • Union-find with path compression and union by rank gives near-constant per operation, where is the inverse Ackermann function — at most 4 for any practical input.
  • A minimum spanning tree is unique if all edge weights are distinct. It always has exactly edges.

Sorting

AlgorithmBestAverageWorstSpaceStableMethod
BubbleYesExchange
SelectionNoSelection
InsertionYesInsertion
MergeYesDivide & conquer
QuickNoDivide & conquer
HeapNoSelection
CountingYesNon-comparison
RadixYesNon-comparison
  • Quicksort's worst case is an already-sorted array with a first-element pivot — every partition splits 1 and n−1.
  • Insertion sort is O(n) on nearly-sorted data and beats everything for n below roughly 10–20, which is why hybrid sorts fall back to it.
  • Mergesort is the standard choice for linked lists (no random access needed, and it can be done with O(1) extra space) and for external sorting of data too large for memory.
  • Timsort (Python, Java objects) is a stable hybrid of mergesort and insertion sort that detects existing runs — O(n) on already-sorted input.

Searching

TechniqueRequiresComplexityNote
Linear searchNothingO(n)The only option on unsorted data
Binary searchSorted, random accessO(log n)Halves the range each step
Jump searchSorted arrayFixed-size jumps then a linear scan
Interpolation searchSorted and uniformly distributed average, O(n) worstEstimates position from the value
Exponential searchSorted, unboundedO(log n)Find a range by doubling, then binary search
Hash lookupA hash tableO(1) averageNo ordering, so no range queries
CBinary search, including the overflow bug
int binary_search(int a[], int n, int key) { int lo = 0, hi = n - 1; while (lo <= hi) { // NOT (lo + hi) / 2 - that overflows once lo + hi exceeds INT_MAX. // This bug sat undetected in the JDK for nine years. int mid = lo + (hi - lo) / 2; if (a[mid] == key) return mid; if (a[mid] < key) lo = mid + 1; else hi = mid - 1; } return -1; }

Algorithm design paradigms

ParadigmIdeaApplies whenExamples
Divide & conquerSplit into independent subproblems, solve, combineSubproblems do not overlapMergesort, quicksort, binary search, Strassen
GreedyTake the locally best choice and never reconsiderThe greedy-choice property and optimal substructure both holdDijkstra, Kruskal, Prim, Huffman, activity selection
Dynamic programmingSolve each overlapping subproblem once and store the resultOptimal substructure and overlapping subproblemsKnapsack, LCS, edit distance, matrix chain, Floyd-Warshall
BacktrackingBuild candidates incrementally, abandon a branch as soon as it cannot workConstraint satisfaction over a search treeN-queens, sudoku, subset sum, graph colouring
Branch & boundBacktracking plus a bound that prunes provably worse branchesOptimisation over a large search space0/1 knapsack, travelling salesman
Classic DP problemStateComplexity
0/1 knapsackdp[i][w] = best value using first i items within weight w — pseudo-polynomial
Longest common subsequencedp[i][j] = LCS of the two prefixes
Edit distancedp[i][j] = edits to turn prefix i into prefix j
Matrix chain multiplicationdp[i][j] = min cost to multiply matrices i..j
Coin change (min coins)dp[a] = fewest coins summing to a
Longest increasing subsequencedp[i] = LIS ending at i, or with binary search

Self-check

12 questions on Data Structures & Algorithms · nothing is stored

0/12 answered
Question 1

1.Which statement about Big-O is correct?

Question 2

2.Building a binary heap from n unsorted elements bottom-up takes:

Question 3

3.Why does Dijkstra's algorithm fail on graphs with negative edge weights?

Question 4

4.Inorder traversal of a binary search tree produces:

Question 5

5.Which sorting algorithm is stable, guaranteed O(n log n), but needs O(n) extra space?

Question 6

6.Deleting an entry from an open-addressed hash table by simply clearing the slot causes:

Question 7

7.Database indexes use B+ trees rather than balanced binary trees mainly because:

Question 8

8.Dynamic programming is preferable to plain divide and conquer when:

Question 9

9.BFS finds shortest paths only when:

Question 10

10.The 0/1 knapsack DP runs in O(nW). Why is that not a polynomial-time algorithm?

Question 11

11.Which is true of a binary heap?

Question 12

12.The Ω(n log n) lower bound on sorting applies to: