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

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

Интерполяционный поиск

Также: Interpolation search · интерполирующий поиск · поиск по интерполяции · выученный индекс · learned index · сужающийся конус · shrinking cone · PGM-index · FITing-tree

Словарь не открывают посередине, когда ищут «яблоко», — его открывают у самого конца. Интерполяционный поиск делает то же с числами: прикидывает место по значениям на краях. На равномерных ключах ему хватает log₂ log₂ n проб — около четырёх на 42 тысячи хешей, у двоичного поиска четырнадцать. На населении городов тот же поиск тратит сотни и тысячи проб. Почему так, как застраховаться и что из этой идеи выросло в базах данных.

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

Сложность

Время
≈ log₂ log₂ n в среднем, n в худшем
Память
O(1)

в среднем — на равномерно распределённых ключах; по очереди с делением пополам — не больше 2⌊log₂ n⌋ + 2 проб; проба дороже, чем у двоичного поиска: в ней умножение и деление

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

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

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

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

Где работает

  • индекс пакета в git: таблица из 256 счётчиков по первому байту хеша сужает двоичный поиск
  • функция oid_pos в git: первая проба по интерполяции, остальные — делением пополам
  • выученные индексы баз данных (FITing-tree, PGM-index): кривая ключей из кусков прямых с ошибкой ±ε
  • словари с вырубками по буквам на обрезе страниц

История

Уэсли Питерсон, 1957

Содержание

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

Когда в словаре ищут «яблоко», его не открывают посередине — открывают у самого конца, потому что «я» — последняя буква. Двоичный поиск так не умеет: он делит отрезок пополам, не глядя на то, что ищет. Интерполяционный поиск смотрит: прикидывает место по значениям на краях отрезка и пробует там. Ниже пять отсортированных рядов из 45 227 городов мира: хеши названий, сами названия как в телефонной книге, широты, население и придуманный ряд с «великаном». Окно сцены следит за отрезком, который остался, и хорда между его краями всегда идёт по диагонали окна.

На хешах цель находится за три-четыре пробы, а двоичному поиску на тех же 42 311 числах нужно четырнадцать-шестнадцать. Хеш перемешивает биты, значения ложатся ровно, и кривая в любом окне — почти диагональ. На населении поиск ползёт: город в 22 026 жителей он находит за 2139 проб. Кривая населения — длинная полка из десятков тысяч городков и обрыв вверх, где миллионники. Хорда идёт высоко над полкой и показывает почти на левый край, а при приближении картина повторяется.

Идея

Интерполяционный поиск устроен как двоичный: проба, сравнение, отрезок сужается. Отличается выбор места. Двоичный поиск берёт середину, а интерполяционный проводит прямую через края отрезка — точки (lo, a[lo]) и (hi, a[hi]) — и пробует там, где она достигает высоты цели x.

Цель стоит выше нижнего края на долю (x − a[lo]) / (a[hi] − a[lo]) всего подъёма. Если значения растут равномерно, на такую же долю отрезка она и отстоит от его начала:

доля = (x − a[lo]) / (a[hi] − a[lo]),
pos = lo + ⌊доля · (hi − lo)⌋.

Дальше всё как у двоичного поиска. Если на месте pos цель — готово; если там меньше — ищем правее pos, если больше — левее. Поиск идёт, пока цель лежит между значениями на краях: при x < a[lo] или x > a[hi] её в отрезке нет. Это условие нужно не только для того, чтобы вовремя остановиться.

Если a[lo] ≤ x ≤ a[hi] и a[lo] < a[hi], то lo ≤ pos ≤ hi.

Из условия 0 ≤ x − a[lo] ≤ a[hi] − a[lo], поэтому доля лежит между 0 и 1, а ⌊доля · (hi − lo)⌋ — между 0 и hi − lo. Прибавив lo, получаем lo ≤ pos ≤ hi.

Без проверки краёв доля выходит за [0, 1], и проба улетает за пределы массива. Когда a[lo] = a[hi], делить нельзя, но тогда все значения отрезка равны и пробовать можно любое место — листинг берёт lo.

Произведение (x − a[lo]) · (hi − lo) для 64-битных ключей не помещается в 64 бита: ключ около 1018, умноженный на миллион мест, — это 1024. В Python целые растут сами. В Go листинг считает произведение в 128 битах — bits.Mul64 — и делит его bits.Div64. Частное не больше hi − lo, поэтому помещается.

Почему так мало проб

Возьмём модель, в которой метод хорош. Пусть n ключей — независимые случайные числа, равномерно разбросанные по отрезку [A, B], и края A и B известны. Ищем число x; пусть K — сколько ключей не больше x, то есть место, куда x встанет. Первая проба угадывает его как np, где p = (x − A)/(B − A).

В этой модели первая проба в среднем ошибается не больше чем на √n / 2 мест: E|K − np| ≤ √n / 2.

Каждый ключ не больше x с вероятностью p, независимо от остальных, поэтому K — сумма n независимых слагаемых, равных 1 с вероятностью p и 0 иначе. Её среднее — np, ровно догадка, а дисперсия E(K − np)² = np(1 − p) ≤ n/4: произведение p(1 − p) не больше 1/4. Средний квадрат величины не меньше квадрата её среднего модуля, поэтому E|K − np| ≤ √(E(K − np)²) ≤ √n / 2.

На миллионе ключей первая проба в среднем промахивается не больше чем на 500 мест — а двоичный поиск после первой пробы знает лишь, в какой половине цель. Дальше та же картина в меньшем масштабе, и тут уже прикидка, а не доказательство. После пробы новая хорда начинается от значения, которое близко к x, и доля p в новой задаче маленькая — порядка ошибки, делённой на длину отрезка m. Дисперсия mp(1 − p) тогда порядка прошлой ошибки, и новая ошибка — порядка корня из прошлой. Отрезок, где ещё может быть цель, сжимается как n, √n, n1/4, … и после k проб он порядка n1/2k. До нескольких мест это доходит, когда 2k ≈ log₂ n, то есть при k ≈ log₂ log₂ n. Для миллиона ключей это 4,3 пробы, для миллиарда — 4,9.

Строго это доказали в 1978 году Иехошуа Перл, Алон Итаи и Хаим Авни: в среднем хватает около log₂ log₂ n проб, и сделать заметно больше почти невероятно. На хешах из сцены log₂ log₂ 42 311 ≈ 3,94, а листинг ниже намерил в среднем 4,0.

Где он ломается

Всё держится на том, что кривая значений близка к прямой. Если это не так, хорда врёт, и можно построить ряд, где она врёт каждый раз.

Пусть n ≥ 2 и массив — 1, 2, …, n − 1 и в конце одно число G ≥ n². Тогда поиск числа n − 1 делает n − 1 проб.

Индексы — с нуля, так что a[i] = i + 1 при i ≤ n − 2 и a[n − 1] = G; цель стоит на месте n − 2. Покажем, что при lo = i ≤ n − 2 и hi = n − 1 проба попадает ровно в lo. Числитель (x − a[lo]) · (hi − lo) = (n − 2 − i)(n − 1 − i) не больше (n − 2)(n − 1) = n² − 3n + 2. Знаменатель a[hi] − a[lo] = G − i − 1 не меньше n² − n + 1 — это больше числителя. Значит, доля · (hi − lo) < 1 и pos = lo. Пока i < n − 2, там стоит i + 1 < x: lo сдвигается на одно место, а hi не меняется. Пробы идут по местам 0, 1, …, n − 2 — всего n − 1, и последняя находит цель.

В сцене это ряд «великан»: 1 … 4999 и 1018. Окно не сужается — слева отщипывается по одному месту, и великан в углу всё время тянет хорду вниз. Настоящие данные ломают поиск мягче, но по той же причине.

  • Население. Медианный город из 45 227 — 25 247 жителей, у 73 % городов меньше 50 000, а самый большой — почти 25 миллионов. Кривая — полка с обрывом, и обрыв играет роль великана: в среднем 1587 проб, в худшем 2211.
  • Телефонная книга. Название — число из первых четырёх букв по основанию 27, как если бы книгу открывали по алфавиту. Но буквы неравны: на S начинается 4898 названий, на X — 211. Интерполяция делает в среднем 17,6 пробы, в худшем 60, а двоичный поиск — 13,1.
  • Широты. Почти две трети городов лежат между 20° и 60° северной широты, к полюсам их почти нет. Хорда, проведённая через крайние широты, промахивается — 16,0 пробы против 14,0.

Хуже всего, что заранее этого не видно: на хешах проб почти вчетверо меньше, чем у двоичного поиска, на названиях — больше, и на одних и тех же данных разные цели ведут себя по-разному.

Страховка

Лечат это просто — чередованием. Нечётная проба — по хорде, чётная — в середину отрезка. Половинные шаги гарантируют, что отрезок сжимается, а хорды сохраняют удачу там, где кривая ровная.

Поиск по очереди на любом отсортированном массиве из n элементов делает не больше 2⌊log₂ n⌋ + 2 проб.

Пусть в отрезке m элементов. Проба в середину mid = ⌊(lo + hi)/2⌋ оставляет либо mid − lo = ⌊(m − 1)/2⌋, либо hi − mid = ⌊m/2⌋ элементов — не больше ⌊m/2⌋. Проба по хорде отрезок не увеличивает: место pos выбывает, и элементов становится не больше m − 1. Пара проб — по хорде, потом в середину — оставляет не больше ⌊(m − 1)/2⌋ ≤ ⌊m/2⌋. После k пар остаётся не больше ⌊n/2k⌋ элементов, а при k = ⌊log₂ n⌋ + 1 это ноль. Значит, поиск кончается не позже чем через 2(⌊log₂ n⌋ + 1) проб.

Худший случай — вдвое хуже двоичного поиска, зато в среднем на наших рядах выходит так:

РядПо хордеПо очередиДвоичный
хеши4,06,714,4
телефонная книга17,612,913,1
широты16,010,214,0
население1587,212,313,9
1 … 4999 и великан2476,04,911,3

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

Кривая из прямых кусков

Разрежем ряд на куски так, чтобы в каждом прямая через первый ключ угадывала место любого ключа с ошибкой не больше ε. Кусок берёт ключи по одному, пока такая прямая есть: каждый ключ разрешает свой отрезок наклонов, и их пересечение сужается, как конус с вершиной в первом ключе. Пересечение опустело — начинается новый кусок. Индекс хранит начала кусков и наклоны. Поиск находит кусок двоичным поиском по началам, прикидывает место по его прямой и ищет цель среди 2ε + 1 соседних мест.

Население городов при ε = 64 укладывается в 18 кусков. Закладок через каждые 129 ключей, которые дали бы то же окно, понадобилось бы 238. Хешам хватает шести кусков вместо 328, широтам — 25 вместо 252. Проб выходит примерно столько же, сколько у двоичного поиска, — выигрыш в памяти: у населения индекс меньше в 13 раз, у хешей — в 55, а последние пробы идут внутри 129 соседних ключей — для 64-битных ключей это один килобайт памяти.

Так устроены выученные индексы баз данных. FITing-tree в 2019 году резал кривую ключей жадным «сужающимся конусом», PGM-index в 2020-м — так, чтобы кусков было как можно меньше, и строил над началами кусков такие же куски. Это интерполяционный поиск, у которого вместо одной хорды на всю кривую — по хорде на каждый почти прямой кусок.

Код

Листинг строит из 45 227 городов те же пять рядов, что и сцена, и ищет в каждом каждое 50-е значение тремя способами: по хорде, по очереди и двоичным поиском. Для каждого способа он печатает среднее и наибольшее число проб.

sorted row                 size  interpolation       by turns         binary
                                    mean   max     mean   max     mean   max
hashes of the names       42311      4.0     9      6.7    15     14.4    16
names, a phone book       17205     17.6    60     12.9    23     13.1    15
latitudes                 32407     16.0    43     10.2    17     14.0    15
populations               30576   1587.2  2211     12.3    23     13.9    15
1 .. 4999 and one giant    5000   2476.0  4951      4.9    15     11.3    13

Наибольшее число проб у великана, 4951, — это утверждение о худшем случае в действии: цель на месте 4950 ищется пробами по местам 0, 1, …, 4950. Поиск по очереди на тех же целях делает не больше 15 проб, а его граница — 2⌊log₂ 5000⌋ + 2 = 26.

Пробы и время

Проба не единица времени. В пробе двоичного поиска сравнения и сдвиг, а в пробе по хорде — ещё вычитания, умножение и деление, в Go к тому же 128-битные. Замер ниже ищет случайные ключи среди n случайных 63-битных — на них кривая ровная, как на хешах.

На миллионе ключей поиск по хорде делает в среднем 4,4 пробы, двоичный — 19,0: в 4,3 раза меньше. А по времени в Go разница почти вдвое, в Python — в 1,4 раза. На тысяче ключей в Python поиск по хорде не выигрывает: интерпретатор тратит на умножение и деление больших чисел столько же, сколько сэкономил на пробах. Выигрыш растёт с n: когда массив не помещается в кеш, дорогой становится каждая проба, а не вычисления в ней.

Похожее видели и другие. Перл, Итаи и Авни в 1978 году писали, что в памяти машины интерполяционный поиск идёт вровень с двоичным и чуть быстрее лишь на файлах больше 5000 записей; выигрыш в пробах важен, когда каждая проба — чтение с диска. В git интерполяцию по хешам пробовали с 2007 года: хеши SHA-1 равномерны, лучшего случая не придумать. В 2017-м её убрали — на git rev-list --objects --all в репозитории ядра Linux она давала 37,4 секунды против 35,4 без неё. А в 2019 году Питер Ван Сандт, Яннис Хронис и Джигнеш Патель показали, что аккуратная реализация, которая переиспользует наклон прошлой хорды, бывает до четырёх раз быстрее двоичного поиска на равномерных данных.

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

СпособПроб в среднемПроб в худшемПамять
двоичный поиск≈ log₂ n⌊log₂ n⌋ + 1O(1)
по хорде≈ log₂ log₂ n на равномерных ключахnO(1)
по очередизависит от данных2⌊log₂ n⌋ + 2O(1)
куски с ошибкой ε—⌊log₂ m⌋ + ⌊log₂(2ε + 1)⌋ + 2O(m) на m кусков

Последняя строка — два двоичных поиска: по m началам кусков и по окну из 2ε + 1 мест. Нарезка на куски жадным конусом — один проход, O(n). Средний случай по хорде — log₂ log₂ n только для равномерных ключей. На других распределениях он может быть каким угодно, вплоть до линейного, и без страховки этот поиск разумно ставить только туда, где форма данных известна заранее.

Где работает

  • git. В индексе пакета объектов есть таблица из 256 чисел: сколько хешей начинаются с байта не больше данного. По первому байту хеша таблица сразу даёт отрезок длиной около n/256, и двоичный поиск идёт уже по нему. Это интерполяция, сделанная заранее и таблицей. А функция oid_pos в коде git делает по хорде только первую пробу — по паре байтов хеша, — а дальше делит пополам.
  • Выученные индексы. FITing-tree, PGM-index и их потомки хранят вместо страниц B-дерева куски прямых с гарантированной ошибкой. Об этом задача статьи.
  • Словари. Вырубки по буквам на обрезе — та же таблица, что в git: доля страниц на каждую букву известна заранее, и интерполировать остаётся внутри буквы.
  • Корни уравнений. Метод хорд ищет корень монотонной функции так же: проводит прямую через края отрезка и берёт точку, где она пересекает ноль. И болезнь та же — на выпуклой функции один край застревает, и сходимость становится медленной. Лекарство тоже: метод Брента, например в scipy.optimize.brentq, перемежает хорды и параболы с делением пополам.

История

Поиск по прикидке места описал Уэсли Питерсон в 1957 году, в статье о том, как находить записи в памяти с произвольным доступом. Через четыре года он придумает циклический избыточный код CRC. Питерсон полагал, что в среднем поиску нужно не меньше половины log₂ n обращений. Оценка оказалась слишком пессимистичной. В 1976 году Эндрю Яо и Фрэнсис Яо сложным комбинаторным рассуждением доказали, что на случайных равномерных таблицах хватает log₂ log₂ n и что заметно меньше в среднем не сможет никакой поиск. В 1977-м Перл и Эдвард Рейнгольд дали наглядное объяснение, а в 1978-м Перл, Итаи и Авни — своё доказательство и опыт: на 400 000 равномерных ключах в среднем 4,28 обращения при log₂ log₂ n = 4,21.

Потом метод десятилетиями считали красивой теорией: в памяти он проигрывал двоичному поиску из-за цены пробы, а на неравномерных данных — из-за числа проб. Вернулся он с выученными индексами: если кривая не прямая целиком, её можно сделать прямой по кускам.

Задачи

В задаче нужно нарезать на прямые куски моменты землетрясений, население и широты городов — тем самым сужающимся конусом — и сосчитать куски. Ключи до 2·1018, и наклоны приходится сравнивать точно.

Индекс из прямых

Данные: Населённые пункты: GeoNames, CC BY 4.0 Землетрясения: каталог USGS, общественное достояние

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

SRT Алгоритм Двоичный поиск Находит границу «нет → да» в упорядоченных данных за log₂ n проверок. O(log n) 2 задачи SRT Алгоритм Экспоненциальный поиск Шаги 1, 2, 4, 8, … пока не перескочишь, потом двоичный поиск: когда граница неизвестна или ответ близко. 2⌊log₂ i⌋ + 1 вопрос до позиции i 1 задача DS Структура данных B-дерево Дерево с сотнями ключей в узле-странице: поиск среди миллиарда ключей читает пять страниц. O(log n) сравнений, O(log_B n) чтений страниц 2 задачи NUM~ Алгоритм Бисекция и метод Ньютона Корень уравнения: деление отрезка пополам надёжно, касательные Ньютона — быстро. бисекция — log₂((b − a)/ε) шагов; Ньютон — около log₂ от числа знаков 2 задачи DS Структура данных Хеш-таблица Поиск, вставка и удаление по ключу в среднем за O(1): ключ превращается в номер ячейки массива. O(1) в среднем 2 задачи TEC Приём Как честно измерить скорость Повторы, прогрев, медиана и минимум, timeit и testing.Benchmark — и типичные способы обмануть себя замером. повторов столько, чтобы замер шёл хотя бы десятую долю секунды 1 задача
← Предыдущая Поиск и сортировка Экспоненциальный поиск Следующая → Поиск и сортировка Троичный поиск

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

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

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

Содержание

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

Задачник

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. Отсечения