Revise on one page
Every phase's costs, top mistakes and terms. Print one phase, or print this page for all of them.
Foundations
dsa.fetehadin.comA data structure is a way of organising data in memory so the operations you need are fast. Learn how they are classified and what makes a good one.
| Array: read item i | O(1) |
| Array: insert at the front | O(n): shift everything |
| Linked list: insert at the front | O(1) |
| Linked list: read item i | O(n): walk from the head |
- βCalling a stack "non-linear". Stacks and queues are linear; only trees and graphs are non-linear here.
- βSaying an array is dynamic. A C++ array has a fixed size;
vectorand linked lists are dynamic. - βConfusing a data structure (how data is stored) with an algorithm (the steps that use it).
An Abstract Data Type says WHAT operations exist; a data structure says HOW they are built. Separating the two is the key idea behind every structure on this roadmap.
| push / pop / top (array implementation) | O(1) |
| push / pop / top (linked implementation) | O(1) |
| Maximum size | array: fixed Β· linked list: limited only by memory |
- βSaying an ADT "is" an array. An ADT has no storage details at all; an array can only implement one.
- βListing implementation details (like
topindex) as part of the ADT's definition. - βForgetting error conditions. "pop on an empty stack is an error" IS part of the ADT.
A pointer holds an address. new asks the heap for memory that lives until you delete it. Master this and linked lists become easy.
&x, *p, p->field | O(1) |
new / delete one object | O(1) (amortised; done by the allocator) |
| Stack memory | automatic: freed when the function returns |
| Heap memory | manual: lives until delete |
- βDereferencing
nullptror an uninitialised pointer, which crashes (segmentation fault). - βUsing memory after
delete(dangling pointer). Set the pointer tonullptrright after. - β
new[]paired withdeleteinstead ofdelete[].
A function that calls itself on a smaller problem until it reaches a base case. You need this for trees (Phase 4) and merge/quick sort (Phase 3).
| factorial(n): time | O(n): n calls, O(1) work each |
| factorial(n): space | O(n) stack frames |
| recursive binary search: time | O(log n) |
| recursive binary search: space | O(log n) stack frames (iterative version: O(1)) |
- βNo base case, or a base case that is never reached (e.g.
factorial(n + 1)). - βForgetting to
returnthe recursive call's result (bs(...)instead ofreturn bs(...)). - βThinking recursion is always slower or always better. It trades stack memory for simpler code.
An algorithm is a finite, unambiguous sequence of steps that solves a problem. Learn the 11 properties every correct algorithm must have.
| findMax: comparisons | exactly n β 1 |
| findMax: time | O(n), and best = worst |
| Space | O(1) |
- βConfusing definiteness (each step has one clear meaning) with finiteness (the algorithm stops).
- βSaying an algorithm needs at least one input. It needs zero or more inputs but at least one output.
- βTreating "a program" and "an algorithm" as the same. A program may run forever (an OS); an algorithm must terminate.
Empirical vs theoretical analysis, qualitative vs quantitative, the three classic avoidable inefficiencies, and the timeβspace trade-off.
| Search with late termination | always n checks |
| Search that breaks early | 1 β¦ n checks |
| Duplicate check with nested loops | O(nΒ²) time, O(1) space |
| Duplicate check with a set | O(n) time, O(n) space β trade-off |
- βSaying empirical analysis is useless. It is useful, but its results depend on the machine, compiler, language and inputs.
- βMixing up "redundant computation" (recomputing the same value in a loop) with "repeated array referencing" (reading
a[i]many times). - βThinking the faster algorithm is always better. It may need far more memory.
Turn code into a formula T(n) using a simple set of counting rules. Loops become sums, and nested loops multiply.
Single loop for(i=0;i<n;i++) k=k+1; cout<<k; | T(n) = 4n + 3 β O(n) |
Nested n Γ n loop with k++ | T(n) = 3nΒ² + 4n + 3 β O(nΒ²) |
Loop where i doubles (i *= 2) | β logβ n iterations β O(log n) |
- βCounting the loop test n times instead of n + 1 (the last, failing test counts too).
- βCounting
k = k + 1as 1 unit. It is 2: one addition and one assignment. - βFor
if/else, adding both branches. Count the condition plus the larger branch only.
The same algorithm can be fast or slow depending on the input. We analyse the best, average and worst case, and usually report the worst.
| Linear search: best (key is first) | O(1): 1 comparison |
| Linear search: average (key present) | (n + 1) / 2 comparisons β O(n) |
| Linear search: worst (last or missing) | n comparisons β O(n) |
| Bubble sort with flag: best / worst | O(n) / O(nΒ²) |
| Space | O(1) |
- β"Best case is when n is small." No: best case is about the arrangement of the input for a given n, never about a small n.
- βThinking the average case is (best + worst) / 2. It is an expected value over all inputs.
- βMixing up the case with the notation. "Worst case" is not the same thing as "Big-O".
O, Ξ©, Ξ, o and Ο describe how fast T(n) grows for large n. Learn the definitions, the order of growth, and how to prove f(n) = O(g(n)) by finding c and k.
| O(1) | constant: array access, push/pop |
| O(log n) | logarithmic: binary search |
| O(n) | linear: linear search, traversal |
| O(n log n) | linearithmic: merge sort, heap sort |
| O(nΒ²) | quadratic: bubble, selection, insertion sort |
| O(nΒ³) | cubic: three nested loops |
| O(2βΏ) | exponential: all subsets, naive Fibonacci |
- βForgetting to state c and k explicitly. A proof without numbers gets partial marks.
- βClaiming 3nΒ² = O(n). False: no constant c makes 3nΒ² β€ cΒ·n for all large n.
- βThinking Big-O means "worst case". Big-O is an upper bound on a function; you can give an upper bound for the best case too.
- Data structure:
- A way of organising data in memory so that the operations you need are efficient.
- Primitive data structure:
- Built-in types operated on directly by the machine: int, char, float, bool, pointers.
- Linear data structure:
- Items form a sequence, each with at most one predecessor and one successor: arrays, linked lists, stacks, queues.
- Non-linear data structure:
- Items branch or connect freely: trees and graphs.
- Static vs dynamic:
- Static structures have a size fixed when they are created (arrays); dynamic ones grow and shrink at run time (linked lists).
- ADT (Abstract Data Type):
- A description of data plus the operations on it, saying what they do but not how they are implemented.
- Abstraction:
- Hiding implementation details behind an interface so users only see what they need.
- Algorithm:
- A finite, well-defined sequence of steps that turns input into the desired output.
- Finiteness:
- An algorithm must stop after a finite number of steps.
- Definiteness:
- Every step is precise and unambiguous.
- Empirical analysis:
- Measuring an algorithm by running it and timing it. Depends on the machine and the input.
- Theoretical analysis:
- Counting basic operations as a function of input size n. Machine-independent.
- Time unit:
- One basic operation (assignment, single I/O, Boolean or arithmetic op, return) counted as 1 in T(n).
- T(n):
- The running time of an algorithm as a function of input size n, in time units.
- Best / average / worst case:
- The running time on the most favourable, typical, and least favourable input of size n.
- Big-O (O):
- Upper bound: f(n) = O(g(n)) if f(n) β€ cΒ·g(n) for all n β₯ k, for some constants c > 0 and k.
- Big-Omega (Ξ©):
- Lower bound: f(n) β₯ cΒ·g(n) for all n β₯ k.
- Big-Theta (Ξ):
- Tight bound: f(n) is both O(g(n)) and Ξ©(g(n)).
- Little-o / little-omega:
- Strict bounds: f grows strictly slower (o) or strictly faster (Ο) than g.
- Time/space trade-off:
- Using more memory to save time (or the reverse), e.g. storing results instead of recomputing them.
- Recursion:
- A function that calls itself on a smaller input, stopping at a base case.
- Base case:
- The input a recursive function answers directly, without recursing.
- Call stack:
- Memory where each active function call keeps its parameters and local variables.
- Pointer:
- A variable that stores a memory address.
- Heap (memory):
- Memory for dynamic allocation with new/delete. Lives until you free it.
- Memory leak:
- Heap memory that is no longer reachable but was never deleted.
- Dangling pointer:
- A pointer to memory that has already been freed.
Linear Structures
dsa.fetehadin.comArrays store items side by side in memory, which is why reading any index is instant. 2-D arrays are stored row by row.
| Access a[i] | O(1) |
| Search (unsorted) | O(n) |
| Insert / delete at position i | O(n): shift items |
| Insert / delete at the end | O(1) if there is space |
| Traverse an R Γ C matrix | O(RΒ·C) |
| Space | O(n) for n items |
- βUsing 1-based indexes. C++ arrays start at 0, so the last valid index is n β 1.
- βWriting
a[n](out of bounds). C++ does not check, so it silently corrupts memory. - βUsing
sizeof(a)/sizeof(a[0])inside a function. There,ais a pointer, so the trick gives the wrong size. Passninstead.
Nodes on the heap, each pointing to the next. Insert at front, end or a position, delete by value, search and traverse, plus the dummy-node trick and the classic pitfalls.
| Insert at front | O(1) |
| Insert at end | O(n), or O(1) with a tail pointer |
| Insert at position k | O(k) |
| Delete by value / search | O(n) |
| Access the i-th item | O(n): no random access |
| Space | O(n) plus one pointer per node |
- βLosing the list by updating
headbefore linking the new node (head = n;first). - βDereferencing
temp->nextwithout checkingtemp != nullptr. - βForgetting the empty-list and delete-the-head special cases.
Each node points both forward and backward. You can walk in both directions and delete a node knowing only its address, at the cost of one extra pointer per node.
| Insert / delete at front or end (with tail) | O(1) |
| Delete a node you already have a pointer to | O(1): no need to find prev |
| Search / delete by value | O(n) |
| Traverse backward | O(n): impossible in a singly list without extra memory |
| Space | O(n), with 2 pointers per node |
- βUpdating
nextbut forgettingprev(or the reverse). Each insert or delete touches both directions. - βForgetting
cur->next->prevwhencur->nextisnullptr(deleting the tail) β crash. - βNot updating
tailwhen deleting the last node.
Last in, first out. The stack ADT (push, pop, peek, isEmpty, isFull) built on a fixed array with a top index, plus overflow and underflow.
| push | O(1) |
| pop | O(1) |
| peek / isEmpty / isFull | O(1) |
| search for a value | O(n): not a stack operation |
| Space | O(MAX), fixed when the array is declared |
- βWriting
st[top++] = xwithtop = -1(writes to index β1). Usest[++top] = x. - βPopping without checking
isEmpty()(underflow). - βPushing without checking
isFull()(writes past the array).
The same stack ADT, but the top is the head of a linked list. No fixed capacity, push and pop stay O(1), and the bracket-matching application.
| push (insert at head) | O(1) |
| pop (delete head) | O(1) |
| peek / isEmpty | O(1) |
| Space | O(n), one node (data + pointer) per item |
- βUsing the tail as the top, making pop O(n).
- βForgetting
deletein pop (memory leak). - βReading
top->dataafter deleting the node. Save the value first.
First in, first out. Why a naive array queue makes dequeue O(n), how a front index makes it O(1), and the wasted-slot problem that leads to the circular queue.
| enqueue (at rear) | O(1) |
| dequeue, naive (shift left) | O(n) |
| dequeue, with front index | O(1) |
| front / isEmpty | O(1) |
| Space | O(MAX) |
- βConfusing front and rear (items enter at the rear, leave at the front).
- βDequeuing without checking for empty.
- βWith a front index, testing
rear == -1for empty. The correct test isfront > rear.
Keep two pointers, front and rear. Enqueue at the rear and dequeue at the front, both O(1), with no capacity limit.
| enqueue (insert after rear) | O(1) |
| dequeue (delete front) | O(1) |
| peek / isEmpty | O(1) |
| Space | O(n) |
- βDequeuing from the tail (needs the previous node, so O(n)).
- βForgetting
rear = nullptrwhen the queue becomes empty. The next enqueue then writes through a dangling pointer. - βEnqueue into an empty queue without setting both front and rear.
Let front and rear wrap around with the modulo operator, so freed slots are reused. All operations O(1) on a fixed array.
| enqueue | O(1) |
| dequeue | O(1) |
| front / isEmpty / isFull | O(1) |
| Space | O(N), and every slot is usable |
- βWriting
rear++instead ofrear = (rear + 1) % N. - βTesting
front == rearfor both empty and full. Without acount(or one spare slot) they look the same. - βPrinting from
0torearinstead of starting atfrontand wrapping.
The standard library versions you will use in real code and on LeetCode, their method names, and the classic traps (pop returns void, empty checks).
| stack push / pop / top | O(1) |
| queue push / pop / front / back | O(1) |
| deque push/pop at either end | O(1) |
| deque d[i] | O(1) |
| Space | O(n) |
- βWriting
int x = s.pop();. It does not compile;pop()returnsvoid. Readtop()first. - βCalling
top()/front()/pop()on an empty container (undefined behaviour). Checkempty(). - βUsing
q.top()on a queue (it isfront()), ors.front()on a stack (it istop()).
- Row-major order:
- How C++ stores a 2-D array: row 0 first, then row 1. Address of a[i][j] = base + (iΒ·cols + j)Β·size.
- Node:
- One element of a linked structure: data plus pointer(s) to other nodes.
- Head:
- Pointer to the first node of a linked list.
- Dummy (sentinel) node:
- A fake node placed before the head so every real node has a predecessor.
- Doubly linked list:
- Each node has both next and prev pointers, allowing backward traversal and O(1) delete given a node.
- Stack:
- LIFO collection: push and pop at the top only.
- LIFO:
- Last In, First Out.
- Overflow / underflow:
- Pushing onto a full stack / popping from an empty one.
- Queue:
- FIFO collection: enqueue at the rear, dequeue from the front.
- FIFO:
- First In, First Out.
- Circular queue:
- Array queue whose indices wrap around with % N so freed slots are reused.
- Deque:
- Double-ended queue: insert and remove at both ends in O(1).
Searching & Sorting
dsa.fetehadin.comCheck every item from left to right until you find the key or run out. Works on any array, sorted or not, but costs O(n).
| Best case | O(1): key at index 0 |
| Average case (found) | (n + 1)/2 comparisons β O(n) |
| Worst case | O(n): last item or not present |
| Needs sorted data? | No |
| Space | O(1) |
- βReturning
-1inside the loop (in anelse), which stops after checking only the first item. - βLooping with
i <= n, one past the end. - βForgetting that an unsorted array cannot use binary search.
On a sorted array, check the middle, then throw away the half that cannot contain the key. Each step halves the problem, so it takes O(log n).
| Best case | O(1): key is at the first mid |
| Average / worst case | O(log n): about βlogβ nβ + 1 comparisons |
| 1,000,000 items | at most 20 comparisons |
| Needs sorted data? | Yes, and random access (an array, not a linked list) |
| Space | O(1) iterative Β· O(log n) recursive (call stack) |
- βUsing
while (low < high)instead oflow <= high. That misses a key when one item is left. - βSetting
low = midorhigh = midinstead ofmid Β± 1, which can loop forever. - βRunning it on unsorted data. The result is meaningless.
Find the smallest remaining item and swap it to the front. Repeat. Simple, at most n β 1 swaps, but always O(nΒ²) comparisons.
| Best case | O(nΒ²): it still scans for the minimum every pass |
| Average case | O(nΒ²) |
| Worst case | O(nΒ²): n(nβ1)/2 comparisons |
| Swaps | at most n β 1 (the fewest of the simple sorts) |
| Space | O(1) |
| Stable Β· in-place | no Β· yes |
- βSwapping inside the inner loop every time a smaller item is found. Swap once, after the inner loop.
- βStarting the inner loop at
j = iinstead ofj = i + 1(compares an item with itself). - βClaiming selection sort has an O(n) best case. It has none; it always scans.
Compare neighbours and swap them if they are out of order. After each pass the biggest remaining item has "bubbled" to the end.
| Best case (already sorted) | O(n): one pass, no swaps, stop |
| Average case | O(nΒ²) |
| Worst case (reversed) | O(nΒ²): n(nβ1)/2 comparisons and swaps |
| Space | O(1) |
| Stable Β· in-place | yes Β· yes |
- βLooping
jup ton - 1in every pass instead ofn - 1 - i. It still works, but does useless comparisons. - βWriting
a[j] >= a[j+1]. Swapping equal items breaks stability. - βReading past the array with
j < nand then accessinga[j + 1].
Grow a sorted part on the left by inserting each new item into its correct place, like sorting cards in your hand. Fast on nearly sorted data.
| Best case (already sorted) | O(n): n β 1 comparisons, 0 shifts |
| Average case | O(nΒ²) |
| Worst case (reversed) | O(nΒ²): n(nβ1)/2 comparisons and shifts |
| Space | O(1) |
| Stable Β· in-place | yes Β· yes |
- βWriting
a[j] >= keyin the while loop. That breaks stability. - βForgetting
j >= 0in the loop condition, which readsa[-1]. - βPutting
a[j + 1] = keyinside the while loop. It goes after the loop.
When records are big, don't move them. Sort an array of pointers (or indices) to the records instead, and only small addresses get swapped.
| Comparisons | same as the underlying sort (here selection: n(nβ1)/2) |
| Data moved per swap | one pointer (8 bytes) instead of one whole record |
| Time | O(nΒ²) with selection sort; O(n log n) with a fast sort |
| Space | O(n) for the pointer array |
| Stable Β· in-place | no Β· no |
- βSwapping the records (
swap(*p[i], *p[min])) instead of the pointers (swap(p[i], p[min])). That defeats the purpose. - βComparing the pointers themselves (
p[j] < p[min]) instead of what they point to (p[j]->score). - βForgetting to initialise
p[i] = &s[i]before sorting.
Insertion sort on items a gap apart, with the gap shrinking to 1. Far-away items move in big jumps, so the final insertion sort has little work left.
| Best case (sorted) | O(n log n) |
| Average case | depends on the gaps; about O(n^1.5) for n/2, n/4, β¦ |
| Worst case (gaps n/2, n/4, β¦) | O(nΒ²) |
| Space | O(1) |
| Stable Β· in-place | no Β· yes |
- βComparing neighbours (gap 1) in every pass, which is just insertion sort.
- βForgetting that the last pass must use gap = 1, or the array may not be sorted.
- βClaiming it is stable. Jumps over a gap can reorder equal keys.
Split the array in half, sort each half recursively, then merge the two sorted halves. Always O(n log n), stable, but needs O(n) extra space.
| Best case | O(n log n) |
| Average case | O(n log n) |
| Worst case | O(n log n) |
| Space | O(n) for the temporary array |
| Stable Β· in-place | yes Β· no |
- βForgetting to copy the leftover items after one half runs out.
- βUsing
<instead of<=in merge, which breaks stability. - βBase case
lo > hionly. A single item (lo == hi) must also stop the recursion.
Pick a pivot, partition so smaller items go left and bigger items go right, then sort each side recursively. Average O(n log n), worst O(nΒ²).
| Best case (pivot splits in half) | O(n log n) |
| Average case | O(n log n) |
| Worst case (sorted input, last-item pivot) | O(nΒ²) |
| Space | O(log n) recursion on average, O(n) worst |
| Stable Β· in-place | no Β· yes |
- βRecursing on
(lo, p)instead of(lo, p β 1). The pivot is already in place. - βForgetting the final
swap(a[i + 1], a[hi])that puts the pivot in place. - βSaying quick sort is always O(n log n).
- Stable sort:
- A sort that keeps equal keys in their original relative order.
- In-place:
- Uses only O(1) extra memory besides the input (recursion stack aside).
- Pass:
- One full sweep of the outer loop of a sort.
- Pointer sort:
- Sorting an array of pointers (or indices) to records instead of moving the records themselves.
- Linear search:
- Check each item in turn. O(n). Works on unsorted data.
- Binary search:
- Repeatedly halve a sorted range by comparing with the middle item. O(log n).
- Divide and conquer:
- Split a problem into smaller parts, solve them recursively, combine the results.
- Pivot:
- The item quick sort partitions around.
- Partition:
- Rearranging a range so items smaller than the pivot come before it and bigger ones after.
- Gap (shell sort):
- The distance between compared items in a shell sort pass; shrinks to 1.
Trees
dsa.fetehadin.comRoot, parent, child, leaf, sibling, depth, height, level, subtree; full, complete and perfect binary trees; and the Node struct with recursive count and height.
| count nodes / leaves | O(n): visits every node once |
| height | O(n) |
| find a value (no ordering) | O(n) |
| Space | O(h) recursion stack, where h is the height |
- βMixing up depth (from the root down to a node) and height (from a node down to its deepest leaf).
- βNot stating your height convention. Here, a single node has height 0 and an empty tree β1. Some books count nodes instead of edges.
- βThinking "complete" and "full" mean the same thing.
Preorder, inorder and postorder (depth-first, recursive) and level order (breadth-first, with a queue). Know them by hand and in code.
| pre / in / post order | O(n): every node once |
| level order | O(n) |
| Space | O(h) recursion for DFS; O(w) queue for level order (w = widest level) |
- βMixing the orders. Remember where the Node goes - pre = N L R, in = L N R, post = L R N.
- βForgetting that "left before right" is the same in all three.
- βUsing a stack for level order (that gives a DFS order instead).
Left is smaller, right is bigger. Insert, search, find min and max by walking one path, and why the tree's shape decides whether that is O(log n) or O(n).
| search / insert, balanced | O(log n) |
| search / insert, skewed | O(n) |
| min / max | O(h) |
| inorder (sorted output) | O(n) |
| Space | O(n) for the nodes, O(h) recursion |
- βWriting
insert(root->left, x);without assigning the result back toroot->left. - βChecking only a node's children, not the whole subtree, when asked "is this a BST?".
- βForgetting the
nullptrcheck before readingroot->data.
Deleting a leaf, a node with one child, and a node with two children by copying the inorder successor. The most-asked BST exam question.
| delete, balanced | O(log n) |
| delete, skewed | O(n) |
| Space | O(h) recursion |
- βCase 3: copying the successor value but forgetting to delete the successor node itself (you get a duplicate).
- βCase 3: searching for the successor in the left subtree. Successor = min of the right subtree.
- βNot returning the new subtree root, so the parent still points at the deleted node.
Build a max-heap in the array, then repeatedly swap the root (the largest item) to the end and sift down. O(n log n) in every case, in-place.
| Build heap | O(n) |
| Each extract-max (swap + heapify) | O(log n) |
| Best / average / worst | O(n log n) |
| Space | O(1) iterative, O(log n) with recursive heapify |
| Stable Β· in-place | no Β· yes |
- βUsing 1-based formulas (2i, 2i+1) with a 0-based array. With 0-based it is 2i+1 and 2i+2.
- βStarting the build loop at i = 0 instead of n/2 β 1, going down to 0.
- βCalling heapify on the whole array instead of the shrinking heap size
end.
Trees where a node can have any number of children, stored with only two pointers per node using the left-child/right-sibling representation.
| visit all nodes | O(n) |
| find the k-th child | O(k) |
| count the children of a node | O(degree) |
| Space | O(n), exactly two pointers per node |
- βLinking a node's second child to the parent instead of to the first child's
nextSibling. - βDrawing siblings as "right child of the parent".
- βForgetting that the root has no siblings.
- Binary tree:
- A tree in which every node has at most two children, left and right.
- Root / leaf:
- The top node (no parent) / a node with no children.
- Depth / height:
- Depth: edges from the root down to a node. Height: edges from a node down to its deepest leaf.
- Full / complete / perfect:
- Full: 0 or 2 children everywhere. Complete: all levels full except the last, filled from the left. Perfect: all levels full.
- BST:
- Binary search tree: for every node, left subtree < node < right subtree.
- Inorder successor:
- The next value in sorted order; for a node with a right child, the minimum of its right subtree.
- Preorder / inorder / postorder:
- Depth-first traversals: Node-Left-Right / Left-Node-Right / Left-Right-Node.
- Level-order traversal:
- Visit a tree level by level using a queue (BFS on a tree).
- General tree:
- A tree whose nodes may have any number of children.
- Left-child, right-sibling:
- Stores a general tree with two pointers per node: first child and next sibling.
- Max-heap:
- A complete binary tree where every parent is β₯ its children; stored in an array.
- Heapify:
- Sift a node down until the heap property holds below it. O(log n).
Hashing
dsa.fetehadin.comA hash function turns a key into an array index, so search is O(1) on average. Collisions, separate chaining, linear probing and the load factor.
| search / insert / delete, average | O(1) |
| search / insert / delete, worst (all keys collide) | O(n) |
| Space | O(M + n) |
- βWith linear probing, stopping a search at a slot holding a different key. Only an empty slot means "not found".
- βForgetting to wrap around with
% Mwhen probing. - βClaiming hashing is always O(1). The worst case is O(n).
- Hash function:
- Maps a key to an index of the hash table, e.g. h(k) = k mod M.
- Collision:
- Two different keys hashing to the same index.
- Separate chaining:
- Each table slot holds a list of all keys that hash there.
- Linear probing:
- On a collision, try the next slot (h + i) % M until an empty one.
- Load factor:
- Ξ± = n / M, how full a hash table is.
Graphs
dsa.fetehadin.comVertices and edges, adjacency, degree, paths, cycles, connected graphs, weighted graphs, and how a tree is a special graph.
| max edges, undirected | V(V β 1)/2 |
| max edges, directed | V(V β 1) |
| sum of all degrees | 2E (each edge counted at both ends) |
| Space | Depends on the representation (next topic) |
- βCounting path length in vertices instead of edges.
- βSaying every graph is connected.
- βForgetting that a cycle must not reuse an edge.
Two ways to store a graph in memory, what each costs, and when to choose which.
| space | matrix O(VΒ²) Β· list O(V + E) |
| is there an edge uβv? | matrix O(1) Β· list O(deg u) |
| visit all neighbours of u | matrix O(V) Β· list O(deg u) |
| add an edge | matrix O(1) Β· list O(1) |
- βSetting only
m[u][v]for an undirected edge. Setm[v][u]too. - βSaying the list is always better. Edge lookup is O(1) only in the matrix.
- βForgetting that an undirected edge appears twice in the list.
One-way edges, in-degree and out-degree, directed paths and cycles, and how the matrix and list change.
| out-degree of u | list O(1) (size of adj[u]) Β· matrix O(V) (row sum) |
| in-degree of u | list O(V + E) Β· matrix O(V) (column sum) |
| space | matrix O(VΒ²) Β· list O(V + E) |
- βAdding both
m[u][v]andm[v][u]for a directed edge. - βFollowing an arrow backwards when tracing a path.
- βMixing up in-degree (arrows coming in) and out-degree (arrows going out).
Breadth-first search with a queue, depth-first search with recursion or a stack, the visited array, and O(V + E).
| BFS / DFS with adjacency list | O(V + E) |
| BFS / DFS with adjacency matrix | O(VΒ²) |
| Space | O(V) for visited plus the queue or stack |
- βNo visited array, so the search loops forever around a cycle.
- βIn BFS, marking a vertex visited when it is dequeued instead of enqueued (it can be enqueued twice).
- βVisiting neighbours in a different order from the one the question uses (usually ascending).
- Graph:
- G = (V, E): a set of vertices and a set of edges between them.
- Degree:
- Number of edges at a vertex. In a digraph, split into in-degree and out-degree.
- Path / cycle:
- A sequence of vertices joined by edges / a path that returns to its start.
- Connected graph:
- There is a path between every pair of vertices.
- Adjacency matrix:
- VΓV table where m[u][v] = 1 (or the weight) if there is an edge uβv. O(VΒ²) space.
- Adjacency list:
- For each vertex, a list of its neighbours. O(V + E) space.
- Directed graph (digraph):
- A graph whose edges are one-way arrows.
- DAG:
- Directed acyclic graph: a digraph with no directed cycles.
- BFS:
- Breadth-first search: explore neighbours in waves using a queue. Finds fewest-edge paths.
- DFS:
- Depth-first search: go as deep as possible, then backtrack, using recursion or a stack.