CS
Алгоритмы
Задачник Базовый курс EN

SRT Поиск и сортировка · Алгоритм

Экспоненциальный поиск

Также: Exponential search · поиск удвоением · doubling search · unbounded search · поиск без верхней границы · галопирующий поиск · galloping search · алгоритм Бентли — Яо · коды Элиаса · Elias gamma · Elias delta

Как искать, когда конца не видно: шаги 1, 2, 4, 8, … пока не перескочишь, и двоичный поиск на последнем отрезке. На истории этого сайта это находит недавнюю перемену за 4 проверки вместо 10. А вопросы такого поиска, записанные нулями и единицами, оказываются кодом числа — и отсюда следует, сколько вопросов нужно на самом деле.

Средний 18 минут поиск двоичный поиск сжатие поисковики классика собеседования

Сложность

Время
2⌊log₂ i⌋ + 1 вопрос до позиции i
Память
O(1)

не зависит от длины данных — только от того, как далеко ответ; вариант Бентли и Яо — log₂ i + 2 log₂ log₂ i + O(1), а log₂ i + O(1) для всех i недостижимо

Знать заранее

Двоичный поиск

В базовом курсе

20. Турнир сортировок

Где работает

  • git bisect, когда хорошая версия неизвестна: шаги назад 1, 2, 4, … от сломанной
  • галоп в слиянии Timsort и в пересечении списков поискового индекса
  • двоичный поиск по ответу без известной верхней границы
  • коды Элиаса γ и δ для сжатия промежутков в поисковых индексах
  • подбор числа повторений при замерах: timeit.autorange в Python увеличивает его, пока замер не станет достаточно долгим

История

Джон Бентли, Эндрю Яо, 1976

Содержание

  1. Идея
  2. Сколько вопросов
  3. Сколько нужно на самом деле
  4. Код
  5. Сколько это стоит
  6. Где работает
  7. История
  8. Задачи

Обычный двоичный поиск начинает с вопроса «где середина?» — и для этого надо знать, где конец. А конец известен не всегда. Сломалась страница, и известно только, что в последней версии она сломана; хорошей версии никто не помнит. Журнал растёт, и его длины не знает никто. Ниже настоящая история этого сайта, 904 коммита, и вопрос «когда появилась эта часть сайта?». Поиск идёт от последнего коммита назад шагами 1, 2, 4, 8, … пока не найдёт коммит, где этой части ещё нет, — и только потом делит пополам.

Каталог алгоритмов появился в третьем с конца коммите, и поиск назад находит это место за 4 проверки, а git bisect по всей истории — за 10. Зато учебник математики появился 420 коммитов назад, и там поиску назад нужно 18 проверок против 10. Цена такого поиска зависит не от длины истории, а от того, как далеко ответ. Если ответ близко — выгодно; если граница неизвестна вовсе — это единственный способ.

Идея

Экспоненциальный поиск состоит из двух фаз. Сначала шаги удваиваются: проверяем позиции 1, 2, 4, 8, … пока ответ не окажется позади. Теперь искомое лежит между последней и предпоследней проверкой, на отрезке от 2k−1 до 2k, и его находит обычный двоичный поиск. Обе фазы занимают примерно log₂ i шагов, где i — позиция ответа, и длина данных нигде не участвует.

Его называют ещё поиском удвоением и галопом. Галоп — тот же приём, когда искать нужно не с начала, а от места, где остановились в прошлый раз: так Timsort сливает серии, а поисковый индекс пересекает списки вхождений. Оба случая разобраны в статьях о Timsort и об инвертированном индексе. Здесь — сам поиск и то, сколько вопросов ему нужно.

Сколько вопросов

Поставим задачу в чистом виде. Загадано целое x ≥ 1, верхней границы нет, а спрашивать можно только о сравнении x с числом. Удвоение спрашивает «x < 2?», «x < 4?», «x < 8?», … пока не услышит «да», а потом выясняет остальные двоичные цифры x по одной, спрашивая «x ≥ t?» для середины оставшегося отрезка.

Поиск удвоением угадывает x ровно за 2⌊log₂ x⌋ + 1 вопрос.

Пусть у x в двоичной записи m + 1 цифра, то есть 2m ≤ x < 2m+1 и m = ⌊log₂ x⌋. На вопросы «x < 2k?» при k = 1, …, m ответ «нет», при k = m + 1 — «да»: это m + 1 вопрос. Теперь известно, что x на отрезке [2m, 2m+1), где 2m чисел, и двоичный поиск делит его пополам m раз — по вопросу на каждую из m младших цифр. Всего 2m + 1.

Запишем ответы: «да» — 1, «нет» — 0. Сначала m нулей и единица, потом m младших цифр x. Это x в двоичной записи, перед которой стоит столько нулей, сколько у x цифр после первой. Такую запись придумал Питер Элиас в 1975 году и назвал её кодом γ. Ответы поиска и есть код числа. Попробуйте сами: загадайте число, и сцена покажет вопросы и строку ответов.

Второй ряд в сцене — улучшение Джона Бентли и Эндрю Яо. Удвоение тратит половину вопросов на то, чтобы узнать, сколько у x цифр, — а это число m + 1 само можно найти тем же удвоением, быстрее. Бентли и Яо так и делают: сначала поиском удвоением находят число цифр, задавая вопросы «x < 22j−1?» и затем «x ≥ 2t−1?», потом выясняют цифры самого x. Ответы складываются в код δ того же Элиаса, и вопросов выходит ⌊log₂ x⌋ + 2⌊log₂(⌊log₂ x⌋ + 1)⌋ + 1: для 1018 — 70 вместо 119. Идею можно повторять и дальше — число цифр числа цифр, — и каждый уровень снимает ещё немного.

Сколько нужно на самом деле

Двоичный поиск на известном отрезке длины N тратит log₂ N вопросов, и меньше нельзя. Без границы можно ли обойтись log₂ x вопросами и небольшой добавкой? Нельзя, и это видно как раз через коды.

Любой способ задавать вопросы-сравнения без границы, который всегда находит x, задаёт для каждого x свою строку ответов. Эти строки образуют префиксный код: если строка для x — начало строки для y, то после этих ответов поиск остановился бы с ответом x, хотя мог быть и y.

Если длины слов префиксного кода из нулей и единиц — ℓ1, ℓ2, …, то Σ 2−ℓi ≤ 1.

Сопоставим слову длины ℓ отрезок двоичных дробей, которые начинаются с этих цифр: 0,w000… … 0,w111…. Его длина — 2−ℓ. Если одно слово не начало другого, их отрезки не пересекаются: они расходятся в первой цифре, где различаются слова. Все отрезки лежат в [0, 1] и не перекрываются, поэтому сумма их длин не больше 1. Для бесконечного кода это верно для каждой конечной части суммы, а значит, и для всей.

Пусть какой-то поиск тратит на x не больше log₂ x + c вопросов. Тогда 2−ℓx ≥ 2−c/x, а сумма Σ 1/x бесконечна — неравенство Крафта нарушено. Значит, такого поиска нет. Тот же довод отсекает и log₂ x + log₂ log₂ x + c: сумма Σ 1/(x log₂ x) тоже бесконечна. Её кусок от 2k до 2k+1 содержит 2k слагаемых не меньше 1/(2k+1(k + 1)), то есть не меньше 1/(2(k + 1)), а сумма таких кусков — половина гармонического ряда. А вот Σ 1/(x (log₂ x)2) конечна, и код δ с его двумя логарифмами логарифма помещается в неравенство с запасом. Граница между возможным и невозможным проходит через то, какие ряды сходятся.

Код

Листинг делает оба дела. Поиск назад от последнего коммита сравнивается с git bisect по всей истории. Потом загаданные числа угадываются удвоением и способом Бентли и Яо, и строки ответов сверяются с кодами γ и δ: программа проверяет, что они совпадают символ в символ.

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

У маленьких чисел способ Бентли и Яо иногда даже проигрывает: для 2 ему нужно 4 вопроса против 3. Выигрыш начинается с чисел в сотни и растёт: на 1018 — 70 вопросов против 119 при 60 цифрах в самом числе.

Сколько это стоит

СпособВопросов до позиции iЧто нужно знать
по одномуiничего
двоичный поиск⌈log₂ N⌉длину N
удвоение и двоичный поиск2⌊log₂ i⌋ + 1ничего
Бентли и Яо⌊log₂ i⌋ + 2⌊log₂(⌊log₂ i⌋ + 1)⌋ + 1ничего

Удвоение выгоднее двоичного поиска по всему отрезку, когда ответ ближе, чем примерно √N от начала: 2 log₂ i < log₂ N ровно тогда, когда i < √N. На истории из 904 коммитов это первые три десятка коммитов от конца. Памяти поиск не требует, а каждая проверка обращается к данным один раз — это важно, когда проверка дорогая: собрать версию, скачать страницу, прочитать блок с диска.

Где работает

  • Поиск сломавшей версии. Если хорошая версия неизвестна, шаги назад 1, 2, 4, … от сломанной находят её, а дальше работает обычный git bisect. Если поломка свежая, это ещё и быстрее.
  • Галоп. Слияние серий в Timsort и пересечение списков вхождений в поисковом индексе ищут следующий элемент не с начала, а от предыдущего, — и платят логарифм расстояния, а не всего списка.
  • Двоичный поиск по ответу без границы. Когда неизвестно, насколько большим может быть ответ, его верхнюю границу находят удвоением, а потом ищут двоичным поиском по ответу.
  • Замеры. timeit.Timer.autorange в Python подбирает число повторений: пробует 1, 2, 5, 10, 20, 50, … пока замер не займёт хотя бы 0,2 секунды. Так же эмпирическую сложность меряют на размерах, которые растут вдвое.
  • Сжатие. Коды Элиаса γ и δ — те самые строки ответов — сжимают промежутки между номерами в поисковых индексах: маленькие числа получают короткие коды, большие — подлиннее, и разделители не нужны. Об этом задача статьи.

История

В 1975 году Питер Элиас в статье о «универсальных» кодах целых чисел построил коды γ и δ и показал, что их длина почти наименьшая из возможных. Через год Джон Бентли и Эндрю Яо в заметке «Почти оптимальный алгоритм поиска без границы» посмотрели на ту же задачу как на поиск: описали удвоение, свой улучшенный способ и бесконечную лестницу дальнейших улучшений и доказали, что лучше в принципе почти нельзя. Связь поиска с префиксными кодами, которая стоит за этой нижней оценкой, — пример того, как теория информации отвечает на вопросы о сравнениях.

Задачи

В задаче список вхождений слова в «Войну и мир» сжат кодами δ — строками ответов поиска Бентли и Яо, — и его нужно раскодировать, читая коды без разделителей.

Сжатый список вхождений

Данные: История коммитов legost.in «Война и мир», Л. Н. Толстой, общественное достояние

Смотрите также

SRT Алгоритм Двоичный поиск Находит границу «нет → да» в упорядоченных данных за log₂ n проверок. O(log n) 2 задачи SRT Приём Двоичный поиск по ответу Если проверить ответ легче, чем найти, и проверка монотонна, — ищем ответ делением пополам. O(log D · стоимость проверки) 2 задачи SRT Алгоритм Timsort Находит готовые упорядоченные куски и сливает их: стек серий, галоп, powersort. O(n log n), на готовых данных O(n) 2 задачи SYS Структура данных Инвертированный индекс и BM25 Словарь «слово → документы, где оно есть»: запрос читает несколько коротких списков вместо всех текстов. O(сумма длин списков слов запроса); «И» галопом — O(m·log(n/m)) 2 задачи SRT Алгоритм Троичный поиск Вершина унимодальной функции: сравнить две точки и отбросить часть отрезка. И почему золотое сечение лучше. O(log(L/ε)) вычислений функции 2 задачи TEC Приём Сложность по замерам Рост времени по замерам: наклон, проверка отношением, экспоненты — и где замер врёт. O(B) на попытку 1 задача
← Предыдущая Поиск и сортировка Двоичный поиск по ответу Следующая → Поиск и сортировка Интерполяционный поиск

Учебник пишет Вячеслав Легостин. Всё бесплатно; код читателей не сохраняется.

Computer Science Каталог алгоритмов Задачник Базовый курс Математика legost.in

Каталог алгоритмов

Содержание

  1. Идея
  2. Сколько вопросов
  3. Сколько нужно на самом деле
  4. Код
  5. Сколько это стоит
  6. Где работает
  7. История
  8. Задачи

Задачник

SRT Поиск и сортировка

  1. Двоичный поиск
  2. Двоичный поиск по ответу
  3. Экспоненциальный поиск
  4. Интерполяционный поиск
  5. Троичный поиск
  6. Простые сортировки
  7. Сортировка слиянием
  8. Timsort
  9. Внешняя сортировка
  10. Быстрая сортировка
  11. Пирамидальная сортировка
  12. Сортировка подсчётом и поразрядная
  13. Нижние границы сравнений
  14. Быстрый выбор
  15. Top-k: k лучших
  16. Перемешивание Фишера — Йетса

DS Структуры данных

  1. Динамический массив
  2. Связный список
  3. Стек и очередь
  4. Монотонный стек
  5. Хеш-таблица
  6. Хеширование кукушки
  7. Фильтр Блума
  8. Корневая декомпозиция
  9. Персистентные структуры
  10. Верёвка
  11. Двоичная куча
  12. Двоичное дерево поиска
  13. АВЛ-дерево
  14. Красно-чёрное дерево
  15. Декартово дерево
  16. Список с пропусками
  17. B-дерево
  18. Косое дерево
  19. Бор (префиксное дерево)
  20. Система непересекающихся множеств
  21. Дерево отрезков
  22. Дерево Фенвика
  23. Разреженная таблица
  24. LRU-кэш

GRA Графы

  1. Как хранить граф
  2. Поиск в ширину
  3. Поиск в глубину
  4. Топологическая сортировка
  5. Компоненты сильной связности
  6. 2-SAT
  7. Мосты и точки сочленения
  8. Алгоритм Дейкстры
  9. A*
  10. Двунаправленный поиск
  11. Алгоритм Беллмана — Форда
  12. Алгоритм Флойда — Уоршелла
  13. Алгоритм Джонсона
  14. Иерархии сжатия
  15. Минимальное остовное дерево
  16. Максимальный поток
  17. Алгоритм Диница
  18. Минимальный разрез
  19. Раскраска графа
  20. Паросочетания в двудольном графе
  21. Венгерский алгоритм
  22. Поток наименьшей стоимости
  23. Эйлеров путь
  24. Наименьший общий предок
  25. Тяжёлые пути
  26. Центроидная декомпозиция
  27. Задача коммивояжёра
  28. PageRank

STR Строки и тексты

  1. Наивный поиск подстроки
  2. Префикс-функция и КМП
  3. Z-функция
  4. Полиномиальный хеш
  5. Алгоритм Рабина — Карпа
  6. Алгоритм Ахо — Корасик
  7. Бойер — Мур
  8. Суффиксный массив
  9. Суффиксный автомат
  10. Преобразование Барроуза — Уилера
  11. Алгоритм Манакера
  12. Редакционное расстояние
  13. BK-дерево
  14. Diff Майерса
  15. Код Хаффмана
  16. LZ77 и LZW

DP Динамическое программирование

  1. Динамическое программирование
  2. Задача о рюкзаке
  3. Наибольшая возрастающая подпоследовательность
  4. Наибольшая общая подпоследовательность
  5. Динамика на отрезках
  6. Динамика на деревьях
  7. Динамика по маскам
  8. Динамика по цифрам
  9. Convex hull trick
  10. Оптимизации Кнута и «разделяй и властвуй»
  11. Seam carving
  12. Алгоритм Витерби
  13. Выравнивание последовательностей
  14. Красивый перенос строк

PAR Парадигмы

  1. Жадные алгоритмы
  2. Перебор с возвратом
  3. Ветви и границы
  4. Разделяй и властвуй
  5. Ближайшая пара точек
  6. Встреча посередине

NUM Числа и алгебра

  1. Алгоритм Евклида
  2. Быстрое возведение в степень
  3. Решето Эратосфена
  4. Тест Миллера — Рабина
  5. ρ-алгоритм Полларда
  6. Китайская теорема об остатках
  7. Длинная арифметика
  8. Умножение Карацубы
  9. Быстрое преобразование Фурье
  10. Теоретико-числовое преобразование
  11. Матрицы и рекуррентности
  12. Метод Гаусса
  13. Генераторы псевдослучайных чисел

GEO Геометрия

  1. Векторы и ориентация
  2. Пересечение отрезков
  3. Выпуклая оболочка
  4. Точка в многоугольнике
  5. Площадь многоугольника
  6. Заметающая прямая
  7. Триангуляция Делоне и диаграмма Вороного
  8. k-d дерево
  9. R-дерево
  10. Вращающиеся калиперы
  11. Упрощение линий Рамера — Дугласа — Пекера
  12. Кривые Гильберта и geohash

NUM~ Численные методы

  1. Бисекция и метод Ньютона
  2. Градиентный спуск
  3. Численное интегрирование
  4. Метод Монте-Карло
  5. Методы Эйлера и Рунге — Кутты
  6. Суммирование Кэхэна
  7. Симплекс-метод

SYS Системы и большие данные

  1. Консистентное хеширование
  2. Рандеву-хеширование
  3. Count-Min Sketch
  4. HyperLogLog
  5. Резервуарная выборка
  6. Дерево Меркла
  7. Ограничение частоты запросов
  8. Инвертированный индекс и BM25
  9. MinHash и LSH
  10. LSM-дерево
  11. Повтор с джиттером
  12. Raft
  13. Часы Лэмпорта и векторные часы
  14. CRDT
  15. Два случайных выбора
  16. Планирование задач
  17. Алгоритм rsync
  18. LFU и ARC

ML Данные и машинное обучение

  1. k-средних
  2. DBSCAN
  3. k ближайших соседей
  4. Решающее дерево
  5. Наивный Байес
  6. Линейная регрессия
  7. Логистическая регрессия
  8. Метод главных компонент
  9. Рекомендации через разложение матрицы
  10. Перцептрон и обратное распространение

RND Случайность

  1. Лас-Вегас и Монте-Карло
  2. Случайные блуждания
  3. Имитация отжига
  4. Генетический алгоритм

TEC Приёмы

  1. O-большое
  2. Быстрый ввод и вывод
  3. Как честно измерить скорость
  4. Префиксные суммы
  5. Данные, удобные кэшу
  6. Два указателя
  7. Сколько операций в секунду
  8. Сложность по замерам
  9. Векторизация
  10. Скользящее окно
  11. Мемоизация
  12. Профилировщики
  13. Числа, которые стоит знать
  14. Амортизационный анализ
  15. Как выбрать структуру данных
  16. Сколько займёт в памяти
  17. Сжатие координат
  18. Битовые трюки
  19. Предподсчёт и таблицы
  20. Отсечения