3.6.11 · D2Sorting & Searching

Visual walkthrough — Binary search — iterative, recursive; O(log n); searching in sorted - rotated arrays

2,126 words10 min readBack to topic

We assume only this: you can count, and you know "sorted" means each number is the one before it. Every symbol below is defined the moment it appears.


Step 1 — What a "sorted list" and an "index" even are

WHAT. A list is a row of boxes. Each box holds a number. The position of a box, counting from on the left, is its index. So in the row below, the box at index holds the value .

WHY start here. The whole algorithm talks about indices (lo, hi, mid) — positions, not values. If we blur "position" and "value" together, nothing later makes sense. So we nail the vocabulary first.

PICTURE. The blue numbers under the boxes are indices; the white numbers inside are values. Notice the values only ever go up as you move right — that "only goes up" property is the single thing binary search will exploit.

Figure — Binary search — iterative, recursive; O(log n); searching in sorted - rotated arrays

Step 2 — The window [lo, hi]: the region still worth searching

WHAT. We draw two markers. (chalk-blue, left) and (chalk-pink, right). Together they fence off a stretch of boxes called the window. At the start the window is the whole list: , , where is how many boxes there are.

Here is the index of the last box — because we started counting at , the last position is one less than the count.

WHY a window. We are going to promise ourselves one thing and never break it:

PICTURE. The shaded band between the blue and pink arrows is the window. Boxes outside it are greyed — already proven irrelevant.

Figure — Binary search — iterative, recursive; O(log n); searching in sorted - rotated arrays

Step 3 — Why we cut at the middle, and how to compute it safely

WHAT. We pick one box inside the window to test: the middle, at index .

Reading it term by term:

  • = how wide the window is (a distance).
  • = half that distance, rounded down (the floor brackets mean "throw away the fraction"). Rounding down guarantees is a real index, never off the right edge.
  • = start at the left marker, walk halfway in. That lands us in the centre.

WHY the middle and not, say, one-third? After we test we throw away everything on one side of it. The side we keep could be either side. To make the worst outcome as small as possible, we want both sides equal — and equal halves happen only when we cut in the centre. Any other cut leaves one side bigger, and the enemy (worst case) always hands you the bigger side.

WHY not the simpler ? Algebraically it's the same number: But can be a huge sum that overflows a fixed-width integer, while is always smaller than , so it can never overflow. Same value, safer road.

PICTURE. The yellow arrow drops onto the centre box. The two brackets show the equal-ish left and right halves it creates.

Figure — Binary search — iterative, recursive; O(log n); searching in sorted - rotated arrays

Step 4 — The three comparisons: which half dies

WHAT. We compare the value at to the target . Exactly three things can happen, and each has a forced response that keeps the promise from Step 2.

  • : the middle value is too small. Since the list only goes up rightward, everything at or left of is also too small. So the target, if it exists, is strictly to the right → move past .
  • : mirror image, target is to the left → pull before .

WHY the (not lo = mid)? We already tested — it's not the answer. Re-including it wastes a comparison and, worse, if the window is size or the marker may never move, and the loop spins forever. The physically shrinks the window, guaranteeing progress.

PICTURE. Three mini-rows, one per case. Greyed boxes = the half we just discarded; the markers have jumped to hug what survives.

Figure — Binary search — iterative, recursive; O(log n); searching in sorted - rotated arrays

Step 5 — When to stop: lo <= hi

WHAT. We repeat Steps 3–4 while the window is non-empty. A window is non-empty exactly when . The moment , the fence has crossed — no boxes are left inside — and we return ("not found").

WHY <= and not <? When the window still holds one untested box. Stopping at would skip it and could miss a single-element match. The equals sign keeps that last box alive.

PICTURE. Left panel: , one lonely box still inside — we must test it. Right panel: markers have crossed, the band is empty, so the search is over.

Figure — Binary search — iterative, recursive; O(log n); searching in sorted - rotated arrays

Step 6 — Counting the steps: where is born

WHAT. Each pass through Steps 3–4 does a fixed amount of work (one comparison, one marker move) and then leaves us with at most half the boxes. Call the number of comparisons needed, worst case, for a window of boxes:

WHY this equation gives a logarithm. Ask: how many times can I halve before I reach a single box? Say it takes halvings:

That last line is the definition of a logarithm: asks " to what power gives ?" — and "what power" is exactly "how many halvings." (The recurrence itself is a recurrence relation; solving it is what gives the .) So:

which the parent formalises under Big-O.

PICTURE. A staircase of shrinking windows: . Count the steps down the stairs — that count is the height of the tree, and the height is .

Figure — Binary search — iterative, recursive; O(log n); searching in sorted - rotated arrays

Step 7 — The rotated array: at least one half is still sorted

WHAT. Take a sorted list and "rotate" it: cut it somewhere and swap the two pieces, e.g. [4,5,6,7,0,1,2]. It's no longer sorted overall — but look closely: on either side of any , at least one half is still perfectly sorted. We detect which by comparing the window's left value to its middle value:

If the left value is the middle value, nothing "wrapped around" between them, so that stretch rises cleanly — it's sorted. Otherwise the wrap (the pivot) sits on the left, which forces the right half to be the clean sorted one.

WHY it still halves. Once we know which half is sorted, we can use ordinary range-checking ("is between these two endpoints?") to decide whether the target lives in the sorted half. Either way we discard one half → still .

WHY the (not ). When the window shrinks so , the "left half" is a single box — trivially sorted — and becomes , which is true. The equals sign keeps that boundary case correct.

PICTURE. The rotated row with the pivot (the drop from to ) marked in pink. The yellow bracket highlights the clean sorted half around a sample .

Figure — Binary search — iterative, recursive; O(log n); searching in sorted - rotated arrays

The one-picture summary

WHAT. One figure stitches the whole journey together: the window narrowing, the middle cut, a half discarded each pass, and the step-count collapsing to .

Figure — Binary search — iterative, recursive; O(log n); searching in sorted - rotated arrays
Recall Feynman: the whole walkthrough in plain words

You've got a row of numbered boxes that only climb as you go right. You want to find one value. Put a blue flag at the far left and a pink flag at the far right — everything between them is "still possibly the answer." Now open the box exactly in the middle. If it's your number, done. If it's too small, slide the blue flag just past the middle — the whole left side, being even smaller, can't hold your number. If it's too big, pull the pink flag just before the middle. Each open box throws away half the boxes between the flags. Keep going while the flags haven't crossed (if they cross, the number was never there). Because you halve the pile every time, a thousand boxes fall to one in about ten opens — that "how many halvings" count is exactly . And if some prankster cut the sorted row and swapped the halves, one side of your middle is always still in order, so you can still tell which side to keep. Same trick, same speed.


Flashcards

Why cut at the middle rather than one-third?
To minimise the worst case: equal halves make the larger surviving side as small as possible, since the worst case always keeps the bigger side.
Where does come from geometrically?
It's the number of times you can halve down to ; — the height of the halving staircase.
In a rotated array, how do you tell which half is sorted?
If the left half is sorted; otherwise the pivot is on the left and the right half is sorted.
Why keep the loop while lo <= hi?
When one untested box remains; < would skip it and miss single-element matches.