Cairn
algorithms · searching · O(log n) comparisons, larger constant than binary search

back to Searching

Ternary Search

Seven of the site's other ten Searching entries solve the same problem — find a target value, whether that requires the array to be sorted first (six of them do) or not (Linear Search doesn't). An eighth, Saddleback Search, solves the same find-a-target-value problem too, just over a matrix instead of an array. Two more are different-question entries in their own right: Quickselect finds a given rank rather than a target value, and Binary Search on Answer doesn't search an array at all, only the space of candidate answers to a feasibility check. Ternary search solves yet another one: find the peak of a unimodal sequence, one that strictly rises to a single maximum and then strictly falls, without evaluating every point. There's no "target" to compare against, only the shape of the values themselves. Instead of one midpoint, it picks two — m1 and m2, splitting the current range into thirds — and compares f(m1) against f(m2). Because the sequence is unimodal, whichever side is still climbing toward the other must contain the peak, so the range can be narrowed by a third every step, the same divide-and-discard shape as binary search, aimed at a different question.

Try it

The array represents a unimodal sequence's values, not sorted keys — index is the input, the number in each cell is the output. peak near center and peak near edge both use a 31-element tent-shaped sequence; step through either one and watch how many iterations it takes to corner the peak. You can also edit the array by hand — see Pitfalls for what happens if what you type isn't actually unimodal.

Press Load, then Step through the search.

Why it works

Each step keeps the invariant binary search keeps too — if the peak exists, it's between lo and hi — just narrowed by a third instead of a half. Given m1 = lo + (hi-lo)/3 and m2 = hi - (hi-lo)/3:

if f(m1) < f(m2):
    # peak can't be left of m1 — a unimodal sequence rising into m1
    # would need f(m1) to be the larger of the two, not the smaller
    lo = m1
else:
    # symmetric argument: peak can't be right of m2
    hi = m2

Note that the range shrinks to [m1, hi] or [lo, m2] — keeping m1 and m2 themselves as candidates, not excluding them the way binary search's mid ± 1 excludes its midpoint once checked. That's deliberate: binary search can exclude mid because it already knows the exact answer isn't there (a direct equality check). Ternary search never gets that certainty from a single comparison — f(m1) < f(m2) only rules out one *side*, and either point can still turn out to be the peak itself, so both stay in play until the range is too small to split further.

Reference implementation

function ternaryMax(arr) {
  let lo = 0, hi = arr.length - 1;
  while (hi - lo > 2) {
    const third = Math.floor((hi - lo) / 3);
    const m1 = lo + third, m2 = hi - third;
    if (arr[m1] < arr[m2]) lo = m1;
    else hi = m2;
  }
  // range is down to at most 3 candidates — finish with a direct scan
  let best = lo;
  for (let i = lo; i <= hi; i++) {
    if (arr[i] > arr[best]) best = i;
  }
  return best;
}

Pitfalls

It silently gives up the wrong answer on a non-unimodal input — checked, not just asserted. Ternary search never verifies its own precondition; it just trusts the sequence has exactly one peak. Feed it this 31-element sequence with two humps, a taller one at index 1 (value 150) and a shorter one at index 18 (value 140):

144, 150, 144, 138, 132, 126, 120, 114, 108, 102, 96,
98, 104, 110, 116, 122, 128, 134, 140, 134, 128, 122,
116, 110, 104, 98, 92, 86, 80, 74, 68

The true maximum is 150 at index 1. Run the reference implementation above on it and it returns index 18, value 140 — a real local peak, but not the global one — in the same 8 iterations the well-behaved presets above take. The first split lands m1=10 in the saddle between the two humps (value 96 — past the tall peak's descent, short of the short one) and m2=20 just past the short peak's own crest at index 18 (value 128, already descending). Since f(m1)=96 < f(m2)=128, that reads as "still climbing to the right," so lo jumps to 10 and indices 0–9 — including the true, taller peak at index 1 — are discarded on the very first step and never reconsidered. No error, no crash, no signal anything went wrong — just a wrong number returned with full confidence.

Using it for sorted-array exact-match search — the problem six of the other ten Searching pages solve — costs more comparisons than binary search, not fewer. It's a natural instinct: if splitting a range in half is good, splitting it in thirds should be better. Checked against exponential search's own 64-element array (0, 3, 6, …, 189), searching every one of its 64 values for itself: binary search needs 328 total comparisons across all 64 targets (average 5.13), a three-way ternary version needs 400 (average 6.25) — worse on 43 of the 64 targets, tied on 10, and better on only 11 (the cases where a lucky m1 or m2 happens to land exactly on the target early). Searching for the middle value, 96, alone: 6 comparisons for binary search, 8 for ternary. The reason is the same three-way split that helps with unimodal peaks costs two comparisons (arr[m1] and arr[m2]) per level instead of binary search's one, while only cutting the range to a third instead of a half — 2 · log₃(n) is mathematically larger than log₂(n) for every n > 1, so the extra comparison per level is never paid back by the extra split.

Complexity

O(log n) either way — finding a unimodal peak takes log₃(n) narrowing steps, each a constant number of comparisons, so it's the same asymptotic order as binary search's log₂(n), just with a larger constant, as the checked example above shows concretely. The two algorithms aren't really competing, though: reach for binary search when the question is "does this exact value exist, and where," reach for ternary search when the question is "where does this sequence peak" — a shape binary search's equality check has no way to answer, since a unimodal sequence has no target to compare against, only a direction to climb. See Choosing a Search Algorithm for how all eleven Searching entries stack up side by side.