CS
Algorithms
Problems Base course RU

SRT Searching and sorting · Algorithm

Exponential search

Also known as: Exponential search · doubling search · unbounded search · galloping search · Bentley–Yao algorithm · Elias codes · Elias gamma · Elias delta

How to search when the end is not in sight: steps 1, 2, 4, 8, … until you overshoot, then binary search on the last stretch. On the history of this site it finds a recent change in 4 checks instead of 10. And the questions of such a search, written as zeros and ones, turn out to be a code of the number, and from this follows how many questions are really needed.

Intermediate 18 minutes searching binary search compression search engines classic interviews

Complexity

Time
2⌊log₂ i⌋ + 1 questions to position i
Memory
O(1)

does not depend on the length of the data, only on how far the answer is; the Bentley–Yao variant is log₂ i + 2 log₂ log₂ i + O(1), and log₂ i + O(1) for all i is unattainable

Know first

Binary search

In the base course

20. The sorting tournament

Where it runs

  • git bisect when a good version is unknown: steps back 1, 2, 4, … from the broken one
  • galloping in Timsort's merge and in intersecting the lists of a search index
  • binary search on the answer with no known upper bound
  • Elias γ and δ codes for compressing gaps in search indexes
  • choosing the number of repetitions in measurements: timeit.autorange in Python increases it until a measurement is long enough

History

Jon Bentley, Andrew Yao, 1976

Contents

  1. The idea
  2. How many questions
  3. How many are really needed
  4. Code
  5. What it costs
  6. Where it is used
  7. History
  8. Problems

Ordinary binary search starts with the question “where is the middle?”, and for that you need to know where the end is. But the end is not always known. A page has broken, and all that is known is that it is broken in the latest version; nobody remembers a good one. A log keeps growing, and nobody knows its length. Below is the real history of this site, 904 commits, and the question “when did this part of the site appear?”. The search goes back from the latest commit in steps of 1, 2, 4, 8, … until it finds a commit where this part does not exist yet, and only then halves.

The algorithms catalogue appeared in the third commit from the end, and the search back finds that place in 4 checks, while git bisect over the whole history takes 10. But the maths textbook appeared 420 commits back, and there the search back needs 18 checks against 10. The cost of such a search depends not on the length of the history but on how far away the answer is. If the answer is near, it pays off; if the bound is unknown altogether, it is the only way.

The idea

Exponential search has two phases. First the steps double: check positions 1, 2, 4, 8, … until the answer is behind. Now the target lies between the last check and the one before it, on the stretch from 2k−1 to 2k, and ordinary binary search finds it. Both phases take about log₂ i steps, where i is the position of the answer, and the length of the data plays no part anywhere.

It is also called doubling search and galloping. Galloping is the same technique when the search must start not from the beginning but from where it stopped last time: this is how Timsort merges runs and how a search index intersects lists of occurrences. Both cases are taken apart in the articles on Timsort and the inverted index. Here it is the search itself and how many questions it needs.

How many questions

Let us state the problem in its pure form. An integer x ≥ 1 is hidden, there is no upper bound, and one may ask only about comparing x with a number. Doubling asks “x < 2?”, “x < 4?”, “x < 8?”, … until it hears “yes”, and then finds out the remaining binary digits of x one at a time, asking “x ≥ t?” for the middle of the remaining stretch.

Doubling search guesses x in exactly 2⌊log₂ x⌋ + 1 questions.

Let x have m + 1 digits in binary, that is, 2m ≤ x < 2m+1 and m = ⌊log₂ x⌋. To the questions “x < 2k?” for k = 1, …, m the answer is “no”, for k = m + 1 “yes”: that is m + 1 questions. Now it is known that x is on the stretch [2m, 2m+1), which holds 2m numbers, and binary search halves it m times, one question for each of the m low digits. 2m + 1 in all.

Write down the answers: “yes” as 1, “no” as 0. First m zeros and a one, then the m low digits of x. This is x in binary, preceded by as many zeros as x has digits after the first. This notation was devised by Peter Elias in 1975, who called it the γ code. The answers of the search are the code of the number. Try it yourself: hide a number, and the scene shows the questions and the string of answers.

The second row in the scene is the improvement by Jon Bentley and Andrew Yao. Doubling spends half its questions finding out how many digits x has, and that number m + 1 can itself be found by the same doubling, faster. Bentley and Yao do exactly that: first they find the number of digits by doubling search, asking “x < 22j−1?” and then “x ≥ 2t−1?”, and then find out the digits of x itself. The answers add up to the δ code of the same Elias, and the questions come to ⌊log₂ x⌋ + 2⌊log₂(⌊log₂ x⌋ + 1)⌋ + 1: for 1018, 70 instead of 119. The idea can be repeated further, the number of digits of the number of digits, and each level takes off a little more.

How many are really needed

Binary search on a known stretch of length N spends log₂ N questions, and fewer is impossible. Without a bound, can one manage with log₂ x questions and a small addition? No, and this is seen precisely through codes.

Any way of asking comparison questions without a bound that always finds x gives each x its own string of answers. These strings form a prefix code: if the string for x is the beginning of the string for y, then after these answers the search would stop with the answer x, although it could have been y.

If the lengths of the words of a binary prefix code are ℓ1, ℓ2, …, then Σ 2−ℓi ≤ 1.

Assign to a word of length ℓ the interval of binary fractions that begin with its digits: 0.w000… … 0.w111…. Its length is 2−ℓ. If one word is not the beginning of another, their intervals do not intersect: they part at the first digit where the words differ. All the intervals lie in [0, 1] and do not overlap, so the sum of their lengths is at most 1. For an infinite code this holds for each finite part of the sum, and so for the whole of it.

Suppose some search spends at most log₂ x + c questions on x. Then 2−ℓx ≥ 2−c/x, and the sum Σ 1/x is infinite: Kraft's inequality is violated. So there is no such search. The same argument rules out log₂ x + log₂ log₂ x + c: the sum Σ 1/(x log₂ x) is also infinite. Its piece from 2k to 2k+1 holds 2k terms each at least 1/(2k+1(k + 1)), that is, at least 1/(2(k + 1)) in all, and the sum of such pieces is half the harmonic series. But Σ 1/(x (log₂ x)2) is finite, and the δ code with its two logarithms of the logarithm fits into the inequality with room to spare. The border between possible and impossible runs through which series converge.

Code

The listing does both jobs. The search back from the latest commit is compared with git bisect over the whole history. Then hidden numbers are guessed by doubling and by the Bentley–Yao method, and the strings of answers are checked against the γ and δ codes: the program verifies that they coincide character for character.

part of the site               first  back from HEAD  bisect all
the maths textbook               483              18          10
the Computer Science course      674              16          10
the Rubik's cube tutorial        415              18          10
the black hole visualisation     652              16          10
this algorithms catalog          901               4          10

            hidden x  doubling  Bentley-Yao  floor log2 x
                   1         1            1             0
                   2         3            4             1
                   5         5            5             2
                 100        13           11             6
             1000000        39           28            19
       1000000000000        79           50            39
 1000000000000000000       119           70            59

For small numbers the Bentley–Yao method sometimes even loses: for 2 it needs 4 questions against 3. The gain begins with numbers in the hundreds and grows: at 1018 it is 70 questions against 119, with 60 digits in the number itself.

What it costs

MethodQuestions to position iWhat must be known
one by oneinothing
binary search⌈log₂ N⌉the length N
doubling and binary search2⌊log₂ i⌋ + 1nothing
Bentley and Yao⌊log₂ i⌋ + 2⌊log₂(⌊log₂ i⌋ + 1)⌋ + 1nothing

Doubling pays off over binary search on the whole stretch when the answer is closer than about √N from the start: 2 log₂ i < log₂ N exactly when i < √N. On a history of 904 commits that is the first thirty or so commits from the end. The search needs no memory, and each check touches the data once, which matters when a check is expensive: building a version, downloading a page, reading a block from disk.

Where it is used

  • Finding the version that broke things. If a good version is unknown, steps back 1, 2, 4, … from the broken one find it, and then ordinary git bisect takes over. If the breakage is recent, this is also faster.
  • Galloping. Merging runs in Timsort and intersecting lists of occurrences in a search index look for the next element not from the start but from the previous one, and pay the logarithm of the distance, not of the whole list.
  • Binary search on the answer with no bound. When it is unknown how large the answer may be, its upper bound is found by doubling, and then it is searched for by binary search on the answer.
  • Measurements. timeit.Timer.autorange in Python chooses the number of repetitions: it tries 1, 2, 5, 10, 20, 50, … until a measurement takes at least 0.2 seconds. Likewise empirical complexity is measured on sizes that double.
  • Compression. Elias γ and δ codes, those very strings of answers, compress the gaps between numbers in search indexes: small numbers get short codes, large ones longer, and no separators are needed. This is the problem of the article.

History

In 1975 Peter Elias, in a paper on “universal” codes for the integers, built the γ and δ codes and showed that their length is nearly the smallest possible. A year later Jon Bentley and Andrew Yao, in the note “An almost optimal algorithm for unbounded searching”, looked at the same problem as a search: they described doubling, their improved method and an infinite ladder of further improvements, and proved that one can hardly do better in principle. The link between search and prefix codes behind this lower bound is an example of information theory answering questions about comparisons.

Problems

In the problem a list of occurrences of a word in “War and Peace” is compressed with δ codes, the strings of answers of the Bentley–Yao search, and it has to be decoded, reading the codes without separators.

A compressed list of positions

Data: Commit history of legost.in War and Peace by Leo Tolstoy, public domain

See also

SRT Algorithm Binary search Finds the “no → yes” boundary in ordered data in log₂ n checks. O(log n) 2 problems SRT Technique Binary search on the answer If checking an answer is easier than finding it, and the check is monotone, search for the answer by halving. O(log D · cost of a check) 2 problems SYS Data structure Inverted index and BM25 A dictionary “word → documents that contain it”: a query reads a few short lists instead of all the texts. O(total length of the query words' lists); AND by galloping O(m·log(n/m)) 2 problems TEC Technique Complexity from measurements The growth of running time from measurements: the slope, the ratio test, exponentials, and where measurement lies. O(B) per attempt 1 problem
← Previous Searching and sorting Binary search on the answer Next → Searching and sorting Interpolation search

Written by Viacheslav Legostin. Everything is free; readers’ code is not stored.

Computer Science The algorithms catalogue Problems Base course Mathematics legost.in

The algorithms catalogue

Contents

  1. The idea
  2. How many questions
  3. How many are really needed
  4. Code
  5. What it costs
  6. Where it is used
  7. History
  8. Problems

Problems

SRT Searching and sorting

  1. Binary search
  2. Binary search on the answer
  3. Exponential search
  4. Interpolation search
  5. External sorting
  6. Heapsort
  7. Counting sort and radix sort

DS Data structures

  1. Dynamic array
  2. Hash table
  3. Cuckoo hashing
  4. Bloom filter
  5. Binary heap
  6. Binary search tree
  7. AVL tree
  8. B-tree
  9. Disjoint set union
  10. Fenwick tree

GRA Graphs

  1. Storing a graph
  2. Breadth-first search
  3. Depth-first search
  4. Bridges and articulation points
  5. Dijkstra's algorithm
  6. A*
  7. Bidirectional search
  8. The Bellman–Ford algorithm
  9. Floyd–Warshall algorithm
  10. Johnson's algorithm
  11. Contraction hierarchies
  12. Dinic's algorithm
  13. Graph colouring
  14. Bipartite matching
  15. Hungarian algorithm
  16. Eulerian path
  17. Heavy paths
  18. Centroid decomposition

STR Strings and text

  1. The Aho–Corasick algorithm
  2. Boyer–Moore
  3. The Burrows–Wheeler transform
  4. Edit distance
  5. BK-tree
  6. Huffman code

DP Dynamic programming

  1. Dynamic programming
  2. Interval DP
  3. Bitmask DP
  4. Digit DP
  5. Convex hull trick
  6. Knuth and divide-and-conquer optimisations

PAR Paradigms

  1. Greedy algorithms
  2. Backtracking
  3. Branch and bound
  4. Divide and conquer
  5. Closest pair of points

NUM Numbers and algebra

  1. Euclid's algorithm
  2. Fast exponentiation
  3. Chinese remainder theorem
  4. Big integers
  5. Fast Fourier transform
  6. Gaussian elimination

GEO Geometry

  1. Vectors and orientation
  2. Convex hull
  3. Delaunay triangulation and Voronoi diagram

NUM~ Numerical methods

  1. Bisection and Newton's method
  2. Gradient descent

SYS Systems and big data

  1. Consistent hashing
  2. Count-Min Sketch
  3. HyperLogLog
  4. Inverted index and BM25
  5. Retry with jitter
  6. CRDT

ML Data and machine learning

  1. k-means
  2. DBSCAN
  3. Decision tree
  4. Perceptron and backpropagation

RND Randomness

  1. Genetic algorithm

TEC Techniques

  1. Big O
  2. Fast input and output
  3. How to benchmark honestly
  4. Cache-friendly data
  5. Complexity from measurements
  6. Amortized analysis
  7. Choosing a data structure
  8. Coordinate compression
  9. Bit tricks