Handbooks / Data Structures & Algorithms / Chapter 3
Sorting and Searching
7 pages · ~5 min✓ Reviewed
Builds on Linked Lists, Stacks and Queues. Next up: Trees and Graphs.
Big-O and Sorting Mental Model
1. Reading Big-O
- Drop constants and lower terms:
3n² + 5n + 2is O(n²) - An upper bound on growth as n grows
- State which case it bounds: best, average or worst
- O(1)
- O(log n)
- O(n)
- O(n log n)
- O(n²)
- O(2ⁿ)
- O(n!)
2. Cases and Space
- Space complexity = extra memory beyond the input
- In-place sorts use O(1) or O(log n) stack
| Sort | Time | Extra space |
|---|---|---|
| Quick | O(n log n) avg, O(n²) worst | O(log n) stack |
| Merge | O(n log n) always | O(n) |
| Insertion | O(n²) avg, O(n) best | O(1) |
3. Sort Properties
- Stable: equal keys keep their original order
- In-place: no big auxiliary array
- Comparison sorts cannot beat Ω(n log n)
Always name the case
Quick sort is O(n log n) average but O(n²) worst. Never quote one bound alone.
Simple Sorts: Bubble and Insertion
1. Bubble Sort
- Swap adjacent out-of-order pairs
- After pass k, the k largest are fixed at the end
- No swap in a full pass → stop: O(n) on sorted input
2. Insertion Sort
- A sorted prefix grows by one each step
- Larger elements shift right; no swaps
- Take key
- Shift larger right
- Drop in gap
python
for i in range(1, len(a)): key, j = a[i], i - 1 while j >= 0 and a[j] > key: a[j + 1] = a[j] j -= 1 a[j + 1] = key
3. Which One
| Bubble | Insertion | |
|---|---|---|
| Best | O(n) early exit | O(n) sorted |
| Avg / worst | O(n²) | O(n²) |
| Space | O(1) | O(1) |
| Stable | yes | yes |
| Moves | ~n²/2 swaps | shifts only |
- Insertion wins on small or nearly sorted arrays
- Timsort and introsort use it for tiny partitions
Pick
Both are O(n²), but insertion is almost always the better one.
Divide and Conquer Sorts: Merge and Quick
1. Merge Sort
- Split in half, sort each, merge two sorted halves with two pointers
- Always O(n log n), extra space O(n), stable
- Ties: take from the left half first (
<=) <on ties breaks stability
python
if left[i] <= right[j]: out.append(left[i]); i += 1 else: out.append(right[j]); j += 1
<= keeps equal keys in order
2. Quick Sort
- Pick a pivot, partition into smaller and larger, recurse
- Average O(n log n), worst O(n²)
- In place, O(log n) stack, not stable
- Worst case: sorted input with first/last pivot
- Fix: random or median-of-three pivot
Bound the stack
Recurse on the smaller side, loop on the larger: O(log n) stack worst case.
3. Which to Pick
| Merge | Quick | |
|---|---|---|
| Worst case | O(n log n) | O(n²) |
| Extra space | O(n) | O(log n) |
| Stable | yes | no |
| Pick for | stability, linked lists, guaranteed bounds | in-place speed, cache-friendly arrays |
Heap Sort and Counting Sort
1. Heap Sort
- Build max-heap in O(n), then swap root with last and sift down
- O(n log n) always, O(1) space, not stable
- 0-based: children of i are
2i+1,2i+2; parent is(i-1)/2 - Heapify from last non-leaf
n/2-1down to0
- Build heapO(n)
- Swaproot with last
- Shrinkheap size - 1
- Sift downrepeat
2. Counting Sort
- Counteach key
- Prefix-sumend positions
- Placeright to left
- O(n + k) time and space
- Right-to-left placement makes it stable
- Only for integer keys in a small range k
- Base of radix sort
3. Compare
| Heap | Counting | |
|---|---|---|
| Time | O(n log n) | O(n + k) |
| Space | O(1) | O(n + k) |
| Stable | No | Yes |
| Keys | comparable | small ints |
Beats n log n
Counting indexes by key value instead of comparing, so the comparison bound does not apply.
- Interview: why does heapify start at
n/2-1? Later nodes are leaves. - Interview: why is counting sort stable? Right-to-left keeps equal keys in order.
Binary Search and Its Variants
1. The Template
- Needs a sorted array; each step halves the range
- Time O(log n), space
O(1)when iterative - Mid is
lo+(hi-lo)/2, not(lo+hi)/2: the sum overflows fixed-width ints
python
def binary_search(a, t): lo, hi = 0, len(a) - 1 while lo <= hi: mid = lo + (hi - lo) // 2 if a[mid] == t: return mid if a[mid] < t: lo = mid + 1 else: hi = mid - 1 return -1
returns -1 if not found
2. Variants
| Variant | On match | Result |
|---|---|---|
| First occurrence | record mid, then hi = mid-1 | leftmost index |
| Last occurrence | record mid, then lo = mid+1 | rightmost index |
- Search on answer: binary search any monotonic predicate (min capacity, square root)
- Rotated array: one half is always sorted; check if the target lies in it, else go to the other half
3. Off-by-one Traps
lo<=hiwithhi=mid: infinite looplo<hiwithhi=mid-1: skips the answer
One template
Pick one template and stay consistent: lo<=hi with mid+1 and mid-1.
Sorting and Searching Cheat Sheet
1. Complexity and Stability
| Sort | Avg time | Space | Stable (typical) |
|---|---|---|---|
| Bubble | O(n²) | O(1) | yes |
| Insertion | O(n²) | O(1) | yes |
| Merge | O(n log n) | O(n) | yes |
| Quick | O(n log n), worst O(n²) | O(log n) | no |
| Heap | O(n log n) | O(1) | no |
| Counting | O(n+k) | O(n+k) | yes |
- Best case O(n): bubble (with early exit) and insertion on sorted input
- Merge and heap stay O(n log n) whatever the input
2. Choosing
| Need | Pick |
|---|---|
| Tiny or nearly sorted | insertion |
| Stable and guaranteed | merge |
| Fastest general in-memory | quick |
| O(1) space with guarantee | heap |
| Small integer keys | counting |
3. Binary Search
- O(log n), sorted input, loop
while lo <= hi mid = lo + (hi - lo) / 2, never(lo + hi) / 2- Lower bound = first index
>=target (first occurrence) - Upper bound = first index
>target
Last occurrence
It is upper bound - 1, not the upper bound itself.
Check yourself
A merge step takes from the right half when left < right is false. Sorting records by key, what breaks?
- On ties the right element goes first, so equal keys swap order.
- Merge sort turns unstable; use
<=to take left on ties.
First-occurrence search for 2 in [1,2,2,2,3] records mid on a match and sets hi=mid-1. What index returns?
- mid=2 matches, hi=1; mid=0 is too small, lo=1; mid=1 matches, hi=0.
- Returns 1, the leftmost 2.
Counting sort fills the output scanning the input left to right with decrementing prefix counts. What goes wrong?
- Prefix sums give the last slot of each key, so the first equal element lands last.
- Equal keys come out reversed: not stable. Scan right to left.