A divide and conquer searching technique. It is similar to binary search but divides the array into three parts and determines which part the key lies in.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
{
"arr": "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]",
"target": 5,
"l": 0,
"r": 9
}Overview
Ternary Search splits the sorted range into three parts using two probes, mid1 and mid2. Depending on how the target compares with these two values, it keeps the left third, the right third, or the middle third.
Although it discards two-thirds of the range per step, it needs up to two comparisons to do so, which makes it slightly slower than binary search for searching sorted arrays. Its real strength is a different problem: finding the maximum or minimum of a unimodal function, where binary search does not apply directly.
In the default example (1..10, target 5), the first probes are index 3 (value 4) and index 6 (value 7). Since 5 lies between them, the range narrows to indices 4..5, and the next probe finds 5 at index 4.
Step by step
- Set
l = 0andr = n - 1. - While
l <= r, computemid1 = l + floor((r - l) / 3)andmid2 = r - floor((r - l) / 3). - If either probe equals the key, return its index.
- If the key is below
arr[mid1], searchl..mid1-1; if abovearr[mid2], searchmid2+1..r; otherwise searchmid1+1..mid2-1. - If the range becomes empty, return -1.
Complexity
| Case | Cost | Why |
|---|---|---|
| Best case | O(1) | A probe hits the target immediately. |
| Average case | O(log3 n) | Two-thirds of the range is discarded per iteration. |
| Worst case | O(log3 n) | About 2·log3(n) comparisons, more than binary search's log2(n). |
| Extra space | O(1) | Iterative implementation. |
When to use it
- Finding the peak or valley of a unimodal function or array, such as an optimal price or launch angle.
- Optimisation over continuous ranges where you can evaluate the function but not its derivative.
- Learning: it is a good way to understand why binary search is optimal for sorted lookups.
Common mistakes
- Using it for plain sorted lookups is usually a de-optimisation compared with binary search.
- On functions with flat regions it cannot tell which side to discard.
- For continuous ranges, a fixed iteration count is safer than a floating-point equality check.
A dry run is the best way to check your understanding of Ternary Search: follow each row, predict the next one, and compare. The run below takes 13 steps in total. Steps that only re-print loop counters are left out so the decisions stand out. The Line column refers to the line numbers in the code listing above.
- Input
- 1,2,3,4,5,6,7,8,9,10
- Target
- 5
- Result
- return value = 4
| Step | Line | Array | What happens |
|---|---|---|---|
| 1 | 1 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | target = 5, l = 0, r = 9 |
| 2 | 2 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | l = 0, r = 9, condition = 9 >= 0 |
| 5 | 6 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | mid1 = 3, condition = 4 == 5 |
| 6 | 8 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | mid2 = 6, condition = 7 == 5 |
| 7 | 11 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | target = 5, mid1 = 3, condition = 5 < 4 |
| 9 | 2 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | l = 4, r = 5, condition = 5 >= 4 |
| 12 | 6 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | mid1 = 4, condition = 5 == 5 |
| 13 | 7 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | return value = 4 |